本篇博客是我个人对B站码场安员外SpringBoot自动配置视频的文字版总结,文中出现的源码均是简化后版本。
原视频链接:https://www.bilibili.com/video/BV1NY411P7VX/?spm_id_from=333.337.search-card.all.click&vd_source=40ac0553f204ea9791dc385431e71f1c

自动配置概念

什么是SpringBoot自动配置

SpringBoot自动配置(Auto-Configiuration):

  • 它是指基于你引入的依赖Jar包,对 SpringBoot应用进行自动配置
  • 它为SpringBoot框架的 “开箱即用” 提供了基础支撑

自动配置(Auto-Configiuration):SpringBoot中的配置类
自动装配(Autowire):Spring中的依赖注入

配置类(Configuration Class):

  • 广义的“配置类”: 被注解 @Component直接或间接修饰的某个类,即我们常说的Spirng组件,其中包含了@Configuration
  • 狭义的“配置类”: 特指被注解@Configuration所修饰的某个类,又称为@Configuration

配置类示例

1
2
3
4
5
6
7
@Configuration
public class Constant{
@Bean
public String beanData(){
return "Bean";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
@ConfigurationProperties(prefix = "simple")
public class SimpleProperties {
private String env;

public String getEnv() {
return env;
}

public void setEnv(String env) {
this.env = env;
}
}

SpringBoot启动流程

SpringBoot启动流程代码简化版:

1
2
3
4
5
6
7
8
9
10
11
12
public static void run(Class<?> primaryClass){
//1.创建一个ApplicationContext实例,即我们常说的IoC容器
ApplicationContext context = createApplicationContext();
//2.将主类(primaryClass)注册到Ioc容器中
loadSourceClass(context, primaryClass);
//3.递归加载并处理所有的配置类
porcessConfigurationClasses(context);
//4.实例化所有的单例Bean
instantiateSingletonBeans(context);
//5.如果是Web应用,则启动Web服务器
startWebServer(context);
}

启动过程

加载并处理所有配置类

加载并处理所有配置类 porcessConfigurationClasses(…)

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void processConfigurationClasses
(ApplicationContext context){
//1.首先从Ioc容器中取出当前存在的源配置类
Class<?> sourceConfigurationClass =
getSourceConfigurationClass(context);
//2.创建一个配置类解析器,然后递归加载并处理应用中所有的配置类
ConfigClassParser parser = new ConfigClassParser(context);
parser.parse(sourceConfigurationClass);
//3.1向Ioc容器中注册@Bean方法对应的BeanDefinition
loadBeanDefinitionsFromBeanMethods(parser.configrarionClasses);
//3.2向Ioc容器中注册ImportBeanDefinitionRegistrar导入的BeanDefinition
loadBeandefinitionsFromRegistrars(parser.configurationClasses);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void parse(Class<?> configClass) {
//1.处理@ComponentScan:根据@ComponentScan扫描指定的package,得到一系列配置类
if (hasComponentScan(configClass)) {
for (Class<?> clazz : doScan(configClass)) {
this.parse(clazz);
}
}
//2.处理注解@Import:根据注解@Import,得到一系列被导入的配置类
if (hasImportedClasses(configClass)) {
for (Class<?> clazz : getImports(configClass)) {
this.parse(clazz);
}
}
//3.处理@Bean方法
processBeanMethods(configClass);
//4.处理@Import导入的ImportBeanDefinitionRegistrar
processRegistrars(configClass);
//5.加入到一个全局的配置类集合中
this.configurationClasses.add(configClass);
}

启动过程

上述过程涉及到两个重要的注解:

@ComponentScan

  • 对指定的package进行扫描,找到其中符合条件的类,默认搜索被@Component修饰的类
  • 通过属性basePackagesbasePackageClasses,来指定要进行扫描的package
  • 如果未指定package,则默认扫描当前@ComponentScan所修饰的类所在的package

使用实例

1
2
3
4
5
6
7
8
9
10
11
12
@ComponentScan
public class Application{...}

@ComponentScan(basePackages = {"zer02.code", "javaSoDifficult.code"})
public class Application{...}

@ComponentScan(excludeFilters =
{@ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@ComponentScan.Filter(
type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public class Application{...}

@Import

  • 提供一种显式地从其他地方加载配置类的方式,这样可以避免使用性能较差的组件扫描(Component Scan)

它支持三种导入方式:

  • 普通类(这里的“普通”,是相对于随后的两个几口而言);
  • 接口ImportSelector的实现类
  • 接口ImportBeanDefinitionRegistrar的实现类

导入普通类

  1. 创建普通的JAVA类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class ConfigA {
    @Bean
    public A a() {
    return new A();
    }
    }

    public class A{

    }
  2. 创建一个配置类,直接将刚才创建的ConfigA导入
    1
    2
    3
    4
    5
    @Configuration
    @Import(ConfigA.class)
    public class ConfigB {

    }
  3. 测试并运行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static void main(String[] args) {
    ApplicationContext ctx =
    new AnnotationConfigApplicationContext(ConfigB.class);
    //现在的Bean ConfigA 和 A 都在Ioc容器中,是可用的
    ConfigA ca = ctx.getBean(ConfigA.class);
    A a = ctx.getBean(A.class);
    System.out.println(ca.getClass().getSimpleName());
    System.out.println(a.getClass().getSimpleName());
    }
    运行结果:
    ConfigA
    A

导入接口ImportSelector实现类

接口ImportSelector中有一个selectImports方法,它的返回值是一个字符串数组,数组中每个元素分别代表一个将被导入的配置类的全限定名。
利用该特性我们可以给Ioc容器动态的导入多个配置类。

  1. 创建普通JAVA类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class ZooConfig {
    @Bean
    public Tiger tiger() {
    return new Tiger();
    }
    }

    public class Tiger {

    }
  2. 创建一个ImportSelector实现类
    1
    2
    3
    4
    5
    6
    public class ZooImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
    return new String[]{"it's so difficultQaQ"};
    }
    }
  3. 创建一个配置类,将ImportSelector实现类导入
    1
    2
    3
    4
    5
    @Configuration
    @Import({ZooImportSelector.class})
    public class ConfigB{

    }
    4.测试并运行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static void main(String[] args) {
    ApplicationContext ctx =
    new AnnotationConfigApplicationContext(ConfigB.class);
    //现在的Bean ZooConfig 和 Tiger 都在Ioc容器中,是可用的
    ZooConfig zc = ctx.getBean(ZooConfig.class);
    Tiger t = ctx.getBean(Tiger.class);
    System.out.println(zc.getClass().getSimpleName());
    System.out.println(t.getClass().getSimpleName());
    }
    运行结果:
    ZooConfig
    Tiger

导入接口ImportBeanDefinitionRegistrar实现类

可以手动将多个BeanDefinition注册到IOC容器,从而实现个性化定制。
利用该特性可以给IOC容器动态的导入多个BeanDefinition。

  1. 创建普通类
    1
    2
    3
    public class Dog {

    }
  2. 创建ImportBeanDefinitionRegistrar实现类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class ZooRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
    BeanDefinitionRegistry registry) {
    GenericBeanDefinition bd = new GenericBeanDefinition();
    bd.setBeanClass(Dog.class);
    registry.registerBeanDefinition("dog", bd);
    }
    }
  3. 创建一个配置类,将ZooRegistrar类导入
    1
    2
    3
    4
    5
    @Configuration
    @Import({ZooRegistrar.class})
    public class ConfigB {

    }
  4. 测试并运行
    1
    2
    3
    4
    5
    6
    7
    8
    public static void main(String[] args) {
    ApplicationContext ctx =
    new AnnotationConfigApplicationContext(ConfigB.class);
    //现在的Bean Dog 在Ioc容器中,是可用的
    Dog dog = ctx.getBean("dog", Dog.class);
    Tiger t = ctx.getBean(Tiger.class);
    System.out.println(dog.getClass().getSimpleName());
    }
    运行结果:
    Dog

总结加载配置类的方式

  • 使用`@Com