SpringBoot2原理篇-黑马

Posted Bug随风舞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot2原理篇-黑马相关的知识,希望对你有一定的参考价值。

 

原理篇 1 自动配置 1.1 bean 的加载方式【一】

1 自动配置

1.1 bean 的加载方式【一】

1.1.1 环境准备

创建一个新的工程模块【Maven 的,不是SpringBoot 的】

 直接创建

 一个全新的Maven 工程

【添加坐标】

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.23</version>
    </dependency>
</dependencies>

 记得刷一下

创建新的bean 类

 再整一套业务层的东西

业务层接口

package com.dingjiaxiong.service;

public interface BookService 
    
    void check();

实现类

package com.dingjiaxiong.service.impl;

import com.dingjiaxiong.service.BookService;

public class BookServiceImpl1 implements BookService 


    @Override
    public void check() 
        System.out.println("book service 1..");
    

复制出三个差不多的实现类

package com.dingjiaxiong.service.impl;

import com.dingjiaxiong.service.BookService;


public class BookServiceImpl2 implements BookService 


    @Override
    public void check() 
        System.out.println("book service 2....");
    
package com.dingjiaxiong.service.impl;

import com.dingjiaxiong.service.BookService;

public class BookServiceImpl3 implements BookService 


    @Override
    public void check() 
        System.out.println("book service 3......");
    
package com.dingjiaxiong.service.impl;

import com.dingjiaxiong.service.BookService;

public class BookServiceImpl4 implements BookService 


    @Override
    public void check() 
        System.out.println("book service 4........");
    

 这样儿差不多准备工作就完成了

1.1.2 第一种方式

Spring 刚出现的时候,它提供的最早的bean 的声明方式就是通过xml 的方式进行声明

来一个配置文件

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--  通过xml的方式声明自己开发的bean  -->
    <bean id="cat" class="com.dingjiaxiong.bean.Cat"/>
</beans>

编写程序运行它

package com.dingjiaxiong.app;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App1 

    public static void main(String[] args) 
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext1.xml");
        Object cat = ctx.getBean("cat");
        System.out.println(cat);
    

运行结果

 这就可以说明cat 这个bean 已经初始化成功了

当然定义时其实也可以不用写 id

<!--  不写id ,通过class获取 -->
<bean class="com.dingjiaxiong.bean.Dog"/>

通过 class直接拿一下

// 通过class,获取bean
        Dog dog = ctx.getBean(Dog.class);
        System.out.println(dog);

运行结果

 没毛病

还有一个小操作,可以一次性到位

String[] names = ctx.getBeanDefinitionNames();
for (String name : names) 
    System.out.println(name);

执行结果

 cat 是配置文件中bean的id,下面的狗是一个全路径的类名,后面还跟着一个 #0 ,意思就是编号,如果我有四个

 就是这样

1.1.3 第三方bean

添加一个依赖坐标

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.11</version>
</dependency>
<!--  xml方式声明第三方开发的bean  -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>

同样直接运行

 xml 方式声明bean 差不多就是这样,

回顾一下

  • XML方式声明bean

 在创建bean的时候可以带上scope属性,scope有下面几种类型

Singleton

这也是Spring默认的scope,表示Spring容器只创建一个bean的实例,Spring在创建第一次后会缓存起来,之后不再创建,就是设计模式中的单例模式。

Prototype

代表线程

Request

表示每个request作用域内的请求只创建一个实例

Session

表示每个session作用域内的请求只创建一个实例

GlobalSession

这个只在porlet的web应用程序中才有意义,它映射到pt的global范围的session,如果普通的web应用使用了这个scope,容器会把它作为普通的session作用域的scope创建。

注解方式

@Component
@Scope("prototype")
public class Student

 
package com.tongda.app;

import com.tongda.domain.Dog;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App1 
    public static void main(String[] args) 
        // 初始化上下文对象
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext1.xml");
        // 通过id,获取bean
        Object cat = ctx.getBean("cat");
        System.out.println(cat);

        // 通过class,获取bean
        Dog dog = ctx.getBean(Dog.class);
        System.out.println(dog);
        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) 
            System.out.println(name);
        
    

这种方式的优点:

  • 全在一个文件中,一目了然

缺点:

  • 写起来麻烦

 

原理篇 1 自动配置 1.2 bean 的加载方式【二】

1 自动配置

1.2 bean 的加载方式【二】

1.2.1 第二种方式

上一次我们已经回顾了一下通过xml 配置文件的形式去定义bean

 其他没啥, 就是特别繁琐【能不能简化?】 【答案是肯定的】

于是Spring 就提供了一种实用注解的方式来定义bean

就我们想把哪个类配置成受Spring 管控的bean,在类上面加注解就行了

package com.tongda.bean;
import org.springframework.stereotype.Component; // @Component代表<bean标签 id="juelan" 省略class=Cat类/> @Component("tom") public class Cat
package com.tongda.bean;

import org.springframework.stereotype.Service;

@Service("jerry")
public class Mouse 

但是这样又有了个新的问题,就这样写就能加载的话,那计算机上这么多类库,岂不是Spring 都要去扫一遍,这样工作量太大

为了降低Spring 的工作强度,还是要配置一下【就你告诉Spring ,去哪儿找】

applicationContext2.xml:注意修改

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
    ">

    <!--  指定加载bean的位置,component组件扫描  -->
    <context:component-scan base-package="com.dingjiaxiong.bean"/>

</beans>

来一个新的运行程序 

package com.tongda.app;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App2 
    public static void main(String[] args) 
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext2.xml");
        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) 
            System.out.println(name);
        
    

直接运行

 可以看到, 东西还有点多

但是tom、jerry 已经加载上了

这就是第二种bean 的加载方式,就是使用组件扫描 + 类上面写注解 来配置

那现在第三方的bean 怎么定义呢?总不能去改人家的源代码加注解吧

是可以做的,先来一个配置类

package com.tongda.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

// 配置第三方bean组件
// @Component
// 或者使用@Configuration配置类
@Configuration
public class DbConfig 

    @Bean
    public DruidDataSource dataSource()
        DruidDataSource ds = new DruidDataSource();
        return ds;
    

修改一下扫描

 再次运行

 可以看到已经上来了,而且dbConfig 也上来了,这倒不奇怪

如果我换个注解

 再次运行

 效果没变,这意味着这两个有关系

 这大致就是使用注解 + 扫描配置 去加载 bean的介绍

回顾一下

XML+注解方式声明bean

 使用@Component及其衍生注解@Controller 、@Service、@Repository定义bean

 

原理篇 1 自动配置 1.3 bean 的加载方式【三】 

1.3.1 第三种方式

之前我们又使用了 xml + 注解的方式加载bean

 这样问题就又来了,如果我那个范围指定的够大

 

 那岂不是这个文件就定死了…【能不能这个文件都不需要呢?】

【答案是肯定的】【配置类取代配置文件

package com.tongda.config;

import org.springframework.context.annotation.ComponentScan;

// 配置类代替xml
@ComponentScan("com.tongda.bean","com.tongda.config")
public class SpringConfig3 

就这样就行了,

来一个全新的应用程序

package com.tongda.app;

import com.tongda.config.SpringConfig3;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;


public class App3 
    public static void main(String[] args) 
        // 加载类配置:AnnotationConfigApplicationContext(类名.class)
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig3.class);
        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) 
            System.out.println(name);
        
    

直接运行

 好像和之前又有些不一样,刚刚写的配置类也上来了

其实是只要咱们用了AnnotationConfigApplicationContext 这玩意儿加载一个类,那个类也会自动变成一个bean

到这儿其实第三种方式就说完了,感觉上其实和第二种没有太大的区别

回顾一下

 @Configuration配置项如果不用于被扫描可以省略

原理篇 1 自动配置 1.4 FactoryBean

1.4.1 FactoryBean

之前我们又完成了使用一个配置类去加载bean

 现在又有人提出疑问了

在我们用Spring 整合Mybatis 或者 Mybatis-Plus 的时候

里面有一种很特殊的bean的声明

 我定义了一个bean ,返回类型是 AA ,但是最后造出来的bean ,不是AA类型的对象【这是怎么回事?】

【这不是一种完整的创建bean 的方式,只能说一种特殊的应用】

举个例子

package com.dingjiaxiong.bean;

import org.springframework.beans.factory.FactoryBean;


public class DogFactoryBean implements FactoryBean<Dog> 

    //第一个方法意思是确定这个工厂造出来的bean是什么
    @Override
    public Dog getObject() throws Exception 
        return new Dog();
    

    //第二个方法要你告诉它造出来这个东西是什么类型
    @Override
    public Class<?> getObjectType() 
        return Dog.class;
    

    @Override
    public boolean isSingleton() 
        return FactoryBean.super.isSingleton();
    

这就是一个生产bean 的工厂

修改一下我们之前的配置类 SpringConfig3

package com.dingjiaxiong.config;

import com.dingjiaxiong.bean.Dog;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;

// 配置类代替xml
@ComponentScan("com.dingjiaxiong.bean","com.dingjiaxiong.config")
public class SpringConfig3 

    @Bean
    public Dog dog()
        return new Dog();
    

其实这样也能直接出一个Dog 对象,直接运行看看

 没毛病,改下方法名

 OK, 方法名就是bean 的名字

现在改一下,换成我们写的工厂

package com.dingjiaxiong.config;

import com.dingjiaxiong.bean.Dog;
import com.dingjiaxiong.bean.DogFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan("com.dingjiaxiong.bean","com.dingjiaxiong.config")
public class SpringConfig3 

    @Bean
    public DogFactoryBean dog()
        return new DogFactoryBean();
    

直接运行

 很明显, dog 对象还是出来了,但是它是啥类型?

 打印出来看看

 可以看到,它并不是工厂,而直接是我们的Dog 类,虽然我们类型写的 工厂,但是返回的确实是Dog 类型

原因就是我们实现了那个接口

 现在这个工厂类一旦被spring 加载到之后,由它造出来的对象不是工厂类型自身,而是后面的泛型类型【妙啊】

类型就在下面

 为什么会有这样子的需求的呢?

原因就在于咱们在返回对象之前,可以在工厂中做一系列的初始化工作

 

 这样就和我们直接new 出来就不一样了【当然不止于此,包括环境监测…】

这就相当于给我们提供了一个造这个对象的工厂,和他的名字也很搭,在这里面可以进行好一系列的出厂前操作【各种前置初始化操作】,做好了再return 出去

而且就DogFactoryBean 本身而言

 它是造不出来自己 的

然后最后那个方法,其实之前学Spring 的时候老师也讲过,就是否单例模式

 

 意思就是现在是单例的,如果我改成false

 效果很明显,现在就不是单例的了

OK,大概就是这样

回顾一下

初始化实现FactoryBean接口的类,实现对bean加载到容器之前的批处理操作

原理篇 1 自动配置 1.5 proxyBeanMethod

1.5.1 @ImportResource

之前我们又完成了使用“工厂” 去创建bean 对象

现在有个新问题又来了

 

 现在的加载方式,有xml 配置文件,也有配置类,如果说我现在有一个十年前写的程序,它是用的配置文件加载的bean, 但是现在要上新功能,用注解写的,如果不能修改配置文件,那咋办?【典型的系统迁移问题:如何在一个新的注解开发的项目中使用老的配置文件的方式?可以实现吗?】

【答案是当然的】

来一个新的配置类

package com.dingjiaxiong.config;

import com.dingjiaxiong.bean.DogFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;

public class SpringConfig32 
    

其实啥也没有

来一个新的运行类去加载32 这个配置类

package com.dingjiaxiong.app;

import com.dingjiaxiong.config.SpringConfig3;
import com.dingjiaxiong.config.SpringConfig32;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App32 

    public static void main(String[] args) 

        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig32.class);

        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) 
            System.out.println(name);
                
    

直接运行看看效果

 OK,没啥问题

现在就来修改32 这个配置类,让它可以去加载到我们使用 xml 配置文件的方式做的bean

其实特简单,一个注解

package com.dingjiaxiong.config;

import com.dingjiaxiong.bean.DogFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;

@ImportResource("applicationContext1.xml")
public class SpringConfig32 

效果很明显,都上来了

这就是在现在的配置类中导入原始的配置文件的做法

回顾一下

  • 加载配置类并加载配置文件(系统迁移) 

 

1.5.2 proxyBeanMethod

之前我们提了一下,@Configuration 这个注解和 @Component 这个注解很像,但是是假的

看看两个注解的源码:

@Component :

 @Configuration:

 注意看这最后一个【研究一下】

再来一个新的配置类

package com.dingjiaxiong.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

@Configuration
public class SpringConfig33 

还是一个空壳

再来一个新的运行类

package com.dingjiaxiong.app;

import com.dingjiaxiong.config.SpringConfig32;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App33 

    public static void main(String[] args) 

        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig33.class);

        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) 
            System.out.println(name);
        
    

直接运行看看效果

 OK, 没啥问题

看看这个bean 的类型

System.out.println("=========================");
System.out.println(ctx.getBean("springConfig33"));

直接运行看看

 这一串什么玩意儿

意思就是SpringConfig33 其实是一个代理对象

现在修改一下33 配置类

 再次运行

 效果很明显,那一串东西没了,就说明现在这个对象,已经不是一个代理对象了,就成了一个原始对象

【所以这玩意儿的作用是什么?】

我们在33 配置类中定义一个 bean

package com.tongda.config;

import com.tongda.bean.Cat;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

// 默认为true:调用方法获取容器中的Bean复用,无论几次都是同一个Bean
// 修改为false:则每次调用方法获取的都是new的出新Bean.
@Configuration(proxyBeanMethods = false)
public class SpringConfig33 

    @Bean
    public Cat cat()
        return new Cat();
    

直接再次运行

 OK, 这没啥,上来了一个bean 对象

现在我们直接用33 这个配置类,去调用这个方法

package com.dingjiaxiong.app;

import com.dingjiaxiong.config.SpringConfig32;
import com.dingjiaxiong.config.SpringConfig33;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App33 

    public static void main(String[] args) 

        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig33.class);

        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) 
            System.out.println(name);
        
        System.out.println("=========================");
        SpringConfig33 springConfig33 = ctx.getBean("springConfig33", SpringConfig33.class);
        System.out.println(springConfig33.cat());
        System.out.println(springConfig33.cat());
        System.out.println(springConfig33.cat());
    

调了三次,而且把proxyBeanMethods 属性值改为了 true

直接运行看看

 可以看到,就是普通的bean对象,而且单例的,即同一个对象

如果改成false

 

很明显,现在就不是单例的了【就成了完全不同的三个对象】

意思就是如果proxyBeanMethods 属性为true,我们在运行对应的方法时,这个方法如果曾经在Spring 容器中加载过bean,那么我们再调,就是直接从容器中去拿了,而不是重新创建

如果属性值为false【不创建代理对象】,就是使用当前类对象去执行它里面的方法,所以才有了三个不同的cat 对象【这就是true 和 false 的区别

【默认是true,即创建代理对象】

看懂了这个,就可以解释我们前面遇到过的一个事情,学mq 的时候

 我把这个注解一关,下面的东西就不能用了,但是我一开 

 因为proxyBeanMethods 属性默认为true,所以这就保证了我们每次都操作的 是同一个队列

如果我明着写成配成false

 这样就绑不对了,压根儿都不是同一个了

OK,差不多就是这样

回顾一下

使用proxyBeanMethods=true可以保障调用此方法得到的对象是从容器中获取的而不是重新创建的

 

原理篇 1 自动配置 1.6 bean 的加载方式【四】

1.6.1 @Import

OK,前面咱们已经学习了三种初始化bean 的方式了,中间还学习了关于 proxyBeanMethods 属性的知识

 接下来就来看看第四种

先来一个新的配置类

package com.dingjiaxiong.config;

public class SpringConfig4 

非常干净的配置类

再来一个新的运行类

package com.dingjiaxiong.app;

import com.dingjiaxiong.config.SpringConfig33;
import com.dingjiaxiong.config.SpringConfig4;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App4 

    public static void main(String[] args) 

        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class);

        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) 
            System.out.println(name);
        
    

直接运行看看

 没啥问题

第四种方式是使用 @Import

package com.dingjiaxiong.config;

import com.dingjiaxiong.bean.Dog;
import org.springframework.context.annotation.Import;

@Import(Dog.class)
public class SpringConfig4 

再次运行

 可以看到,这个东西确实上来了

看看是不是真的加载到了

 OK,没问题,确实加载到了Dog

而且这种方式加载出来的类是一个非常标准的全路径类名,而且是大写开头

【所以这种方式的作用体现在哪里?】

之前我们加注解、写配置,现在使用直接导入,原本的类咱们压根儿没动过

 可以进行有效的解耦,开发中实践

Spring 倡导无侵入式 编程或者叫无入侵式编程

说加就加、看不到动过的痕迹

就现在这种直接导入的方式会是一种非常好用的最佳实践,以后咱们有了一个外部类,可以完全不做改变的把搞成一个bean放入我们的容器

OK, 回顾一下

使用@Import注解导入要注入的bean对应的字节码

【@Import 的扩展】

我现在给它导入一个配置类 

 

package com.dingjiaxiong.config;

import com.dingjiaxiong.bean.Dog;
import org.springframework.context.annotation.Import;


@Import(Dog.class, DbConfig.class)
public class SpringConfig4 

直接运行

 可以看到直接就上来了俩,而且是全路径类名,而且它里面的bean 也被加载了

如果把配置类上的注解拿掉

 这就变成Dog 了

直接运行看看效果

 DbConfig 上来是应该的,和Dog 原理一样,而且可以看到,它里面定义的bean 还是上来了

就是这样

原理篇 1 自动配置 1.7 bean 的加载方式【五】

1.7.1 register

之前我们又说了一种加载bean 的方式,使用@Import 注解可以无侵入的将一个普通类变成一个bean 放到容器中进行管理。

 现在来说第五种:

这种方式平时开发的时候不常用,但是如果是要做一些框架的话就可能会用到…【啊这】

直接开干,先来一个运行程序

package com.dingjiaxiong.app;

import com.dingjiaxiong.config.SpringConfig33;
import com.dingjiaxiong.config.SpringConfig4;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App5 

    public static void main(String[] args) 

        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class);

        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) 
            System.out.println(name);
        
        System.out.println("=========================");        
    

直接运行一下看看

 OK,出来了这些东西无所谓

第五种方式就是在上下文对象已经初始化完毕之后,手工加载bean

ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class);

 不要配置类、也不要配置文件【直接编程干】

【怎么做?】

package com.dingjiaxiong.app;

import com.dingjiaxiong.bean.Cat;
import com.dingjiaxiong.config.SpringConfig33;
import com.dingjiaxiong.config.SpringConfig4;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App5 

    public static void main(String[] args) 

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class);
        //在上下文对象已经初始化完毕之后,手工加载bean

        ctx.registerBean("tom", Cat.class);
        
        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) 
            System.out.println(name);
        
        System.out.println("=========================");

    

现在就手工加载了一个tom

直接运行,看看效果

 没啥毛病,上来了

如果多复制几个, 看看会不会有冲突

 运行结果

 可以看到,出来了, 说明没有冲突

那究竟是哪个留下来了,验证一下,修改一下实体类

package com.dingjiaxiong.bean;

import org.springframework.stereotype.Component;

//这个注解就代表了<bean> 这个标签
@Component("tom")
public class Cat 
    public Cat()
    

    int age;
    public Cat(int age) 
        this.age = age;
    

    @Override
    public String toString() 
        return "Cat" +
                "age=" + age +
                \'\';
    

再修改一下运行类

package com.dingjiaxiong.app;

import com.dingjiaxiong.bean.Cat;
import com.dingjiaxiong.config.SpringConfig33;
import com.dingjiaxiong.config.SpringConfig4;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App5 

    public static void main(String[] args) 

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class);
        //在上下文对象已经初始化完毕之后,手工加载bean

        ctx.registerBean("tom", Cat.class,0);
        ctx.registerBean("tom", Cat.class,1);
        ctx.registerBean("tom", Cat.class,2);

        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) 
            System.out.println(name);
        
        System.out.println("=========================");
        System.out.println(ctx.getBean(Cat.class));

    

直接运行,看看是哪只猫

 OK,效果很明显,最后一个留下来了【就像map,因为key 一样,最终只留了最后那个

这个很有用,假如说现在系统里面有个bean,现在再来一个,把原来那个覆盖了,这就相当于把现有东西给隐藏掉了,只有新的生效了

即一个新的值替换老的值【这个场景就很多了,不做配置用老的值,但凡我一做,就用我们自己的值】

最后再提一个,如果我们仅仅是想注册进去一个bean,还可更简单

并不是全路径

OK, 回顾一下

  • 使用上下文对象在容器初始化完毕后注入bean

原理篇 1 自动配置 1.8 bean 的加载方式【六】

1.8.1 ImportSelector

OK,上一节又说完了第五种

使用上下文对象在容器初始化完毕后注入bean

 下面就来说说第六种

第六种方式平时咱们自己用的少,但是在框架内部大量使用

先来一个全新的类

package com.dingjiaxiong.bean;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportSelector implements ImportSelector 

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) 
        return new String[]"com.dingjiaxiong.bean.Dog";
    

 数组中是要加载bean 的全路径名

再来一个新的配置类

package com.dingjiaxiong.config;

import com.dingjiaxiong.bean.Dog;
import com.dingjiaxiong.bean.MyImportSelector;
import org.springframework.context.annotation.Import;

@Import(MyImportSelector.class)
public class SpringConfig6 

最后来个运行类

package com.dingjiaxiong.app;

import com.dingjiaxiong.bean.Cat;
import com.dingjiaxiong.bean.Mouse;
import com.dingjiaxiong.config.SpringConfig4;
import com.dingjiaxiong.config.SpringConfig6;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App6 

    public static void main(String[] args) 
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig6.class);
        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) 
            System.out.println(name);
        
        System.out.println("=========================");
    

直接运行

 效果很明显,已经上来了

还可以写多个

 加载就是这样了,难免会有人想问,为嘛要折腾这?

这种方式的关键

 

在于这个东西

Metadata 元数据

举个例子,我们造了一个数据库、表,表里面有数据,那么这个数据表的信息在哪儿描述?

还得有另外一张表来描述这张表的结构,那这另外这张表就可以称为被描述表的元数据

还可以有元元数据、元元元数据…【数据库中有四层】

在我们这儿,元数据就是往上追溯一下

【这个东西有什么用?】

修改一下

package com.dingjiaxiong.bean;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportSelector implements ImportSelector 

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) 

        System.out.println(importingClassMetadata.getClassName());

        return new String[]"com.dingjiaxiong.bean.Dog";
    

先拿个classname 看看

 意思就是 importingClassMetadata 这个形参描述的是 SpringConfig6

简单的说,

 加载的是谁,描述的就是谁

再来

System.out.println(importingClassMetadata.hasAnnotation("org.springframework.context.annotation.Configuration"));

这个意思就是描述的那个东西上面有没有Configuration 注解

直接看看

 如果我注掉

 这样就false了

所以现在就可以明确了 importingClassMetadata 这个指的就是它那个类出现在谁上面

 意思就是SpringConfig6 就是它对应的元数据

再来一个

 现在我想知道,它有没有加basePackages 这个属性

package com.dingjiaxiong.bean;

import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

import java.util.Map;

public class MyImportSelector implements ImportSelector 

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) 

        System.out.println("===========================");

        System.out.println(importingClassMetadata.getClassName());
        System.out.println(importingClassMetadata.hasAnnotation("org.springframework.context.annotation.Configuration"));
        Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes("org.springframework.context.annotation.ComponentScan");

        System.out.println(attributes);
        
        System.out.println("===========================");
        return new String[]"com.dingjiaxiong.bean.Dog";
    

直接运行

 没啥毛病

到现在大概就知道了,我们可以利用这个东西去做一系列的判定,ImportSelector 的意义就在于我们可以进行各种条件的判定,判定完毕后,决定是否装载指定的bean,动态加载bean

举个栗子

package com.dingjiaxiong.bean;

import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

import java.util.Map;

public class MyImportSelector implements ImportSelector 

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) 

        boolean flag = importingClassMetadata.hasAnnotation("org.springframework.context.annotation.Configuration");
        if (flag)
            return new String[]"com.dingjiaxiong.bean.Dog";
        
        return new String[]"com.dingjiaxiong.bean.Cat";
    

判定运行结果

 就是这样,意思就是这个东西不仅能够加载bean,它还可以进行条件判定、然后控制加载谁,动态加载bean

package com.tongda.bean;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

import java.util.Map;

public class MyImportSelector implements ImportSelector 

    @Override
    // 牢记:Metadata,meta元data数据,元数据又称中介数据、中继数据
    public String[] selectImports(AnnotationMetadata importingClassMetadata) 
        System.out.println("=============");
        System.out.println("提示:" + importingClassMetadata.getClassName());
        // 注解全类名;@Configuration
        importingClassMetadata.hasAnnotation("org.springframework.context.annotation.Configuration");
        // 获取注解的属性集合
        Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes("org.springframework.context.annotation.ComponentScan");
        System.out.println(attributes);
        System.out.println("=============");
        // 各种条件的判定,判定完毕后,决定是否装在指定的bean,动态记载bean
        boolean flag = importingClassMetadata.hasAnnotation("org.springframework.context.annotation.Configuration");
        if (flag) 
            return new String[]"com.tongda.bean.Dog",;
        
        return new String[]"com.tongda.bean.Cat";
        // return new String[0];
        // 全路径类名
        // return new String[]"com.tongda.bean.Dog","com.tongda.bean.Cat",;
    

    /*@Override
    public Predicate<String> getExclusionFilter() 
        return ImportSelector.super.getExclusionFilter();
    */

 

OK,回顾一下

导入实现了ImportSelector接口的类,实现对导入源的编程式处理

基础篇

1、SpringBoot入门案例

1.1 IDEA联网版

官方URL https://start.spring.io
注:如果创建不了,更改URL https://start.aliyun.com

① 创建新模块

② 选择当前模块需要使用的技术集

③ 开发控制器类

④ 运行自动生成的Application类

⑤ 运行测试

注:Spring程序与SpringBoot程序对比

1.2 官网创建版

官网链接

1.3 手工制作版

① 创建新模块

② 导入坐标

③ 制作引导类

④ 运行测试

1.4 隐藏指定文件/文件夹

2、入门案例解析

2.1 SpringBoot简介

2.2 parent


2.3 starter

2.4 parent和starter对比

2.5 引导类

2.6 内嵌tomcat

2.7 辅助功能

2.8 内置服务器

3、基础配置

3.1 属性配置


springboot Application文档

3.2 不同配置文件优先级



3.3 解决yaml和yml文件没提示

3.4 配置文件书写格式

① yaml语法规则


② 字面值表示方式

③ 数组表示方式

country: china
province: guangdo
city: shanwei
area: chengqu

port: 8080

party: true

birthday: 2000-07-29

user1:
  name: zyy
  age: 22

a:
  b:
    c:
      d: 123

likes1:
  - game
  - music
  - sleep


likes2: [ game,music,sleep ]

users1:
  - name: zyy
    age: 23
  - name: zqh
    age: 24

users2:
  - name: zyy
    age: 23

  - name: zqh
    age: 24

users3: [  name: zyy,age: 23 , name: zqh,age: 24  ]


baseDir: c:\\windows

# 使用$属性名 引用数据
temoDir1: $baseDir\\temp

# 使用引号包裹的字符串,其中的转义字符可以生效
temoDir2: "$baseDir\\temp \\t1 \\t2 \\t3"

3.5 读取yaml配置文件的数据


@RestController
@RequestMapping("/books")
public class BookController 

    //读取yaml数据中的单一数据
    @Value("$country")
    private String country;

    @Value("$user1.name")
    private String name;

    @Value("$likes1[1]")
    private String likes1;

    @Value("$users1[0].age")
    private int age;

    @Value("$temoDir1")
    private String temoDir1;

    @Value("$temoDir2")
    private String temoDir2;

    @GetMapping
    public String getById() 
        //country ====== china
        System.out.println("country ====== " + country);

        //name ====== zyy
        System.out.println("name ====== " + name);

        //like1 ====== music
        System.out.println("like1 ====== " + likes1);

        //age ====== 23
        System.out.println("age ====== " + age);

        //temoDir ====== c:\\windows\\temp
        System.out.println("temoDir1 ====== " + temoDir1);

        //temoDir2 ====== c:\\windows	emp 	1 	2 	3
        System.out.println("temoDir2 ====== " + temoDir2);
        return "SpringBoot is running...";
    


3.6 解决读取yaml配置文件数据的痛点


@RestController
@RequestMapping("/books")
public class BookController 

    //读取yaml数据中的单一数据
    //使用自动装配将所有的数据封装到一个对象Environment中
    @Autowired
    private Environment env;
    

    @GetMapping
    public String getById() 
        //china
        System.out.println(env.getProperty("country"));
        //zyy
        System.out.println(env.getProperty("user1.name"));

        return "SpringBoot is running...";
    


3.7 封装数据

// 1、定义数据模型封装yaml文件中对应的数据
// 2、定义为spring管控的bean
@Component
// 3、指定加载的数据
@ConfigurationProperties(prefix = "datasource")
public class MyDataSource 

    private String driver;
    private String url;
    private String username;
    private String password;

    public String getDriver() 
        return driver;
    

    public void setDriver(String driver) 
        this.driver = driver;
    

    public String getUrl() 
        return url;
    

    public void setUrl(String url) 
        this.url = url;
    

    public String getUsername() 
        return username;
    

    public void setUsername(String username) 
        this.username = username;
    

    public String getPassword() 
        return password;
    

    public void setPassword(String password) 
        this.password = password;
    

    @Override
    public String toString() 
        return "MyDataSource" +
                "driver='" + driver + '\\'' +
                ", url='" + url + '\\'' +
                ", username='" + username + '\\'' +
                ", password='" + password + '\\'' +
                '';
    

4、整合第三方技术

4.1 整合Junit



4.2 整合MyBatis

① 创建新模块

② 选择当前模块需要使用的技术集

③ 设置数据源参数

④ 定义数据层接口与映射配置

⑤ 运行测试

4.3 整合MyBatis常见问题

4.4 整合MyBatis-Plus

① 手动添加SpringBoot整合MyBatis-Plus的坐标

② 定义数据层接口与映射配置

③ 设置相关的配置文件

4.5 整合Druid

5、SSMP整合案例制作

5.1 导入lombok


5.2 数据库id自增

5.3 开启日志


5.4 配置分页插件

5.5 条件查询功能


5.6 业务层快速开发




5.7 数据统一


5.8 异常信息处理



5.9 前后端协议联调

以上是关于SpringBoot2原理篇-黑马的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot2:基础篇(黑马程序员:P1~P53)

SpringBoot2:开发实用篇(黑马程序员P67~P142)

SpringBoot2:开发实用篇(黑马程序员P67~P142)

SpringBoot2:开发实用篇(黑马程序员P67~P142)

SpringBoot2:运维实用篇(黑马程序员P54~P66)

SpringBoot2:运维实用篇(黑马程序员P54~P66)