引导加载自动配置类

我们从主启动程序入手分析。

image-20201225152004423

1
2
3
4
5
6
7
8
@SpringBootApplication
public class SpringbootApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}

}

这里标注了一个@SpringBootApplication注解,我们点击查看源码。

image-20201225152316996

这个注解除了元注解,标注了三个其他的注解。

1
2
3
4
5
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

@SpringBootConfiguration

我们首先来分析一下@SpringBootConfiguration注解,查看源码

image-20201225152516443

1
2
3
4
5
6
@Configuration//标志当前是一个配置类
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;

}

这个类除了元注解就只有@Configuration,所以它的作用也就是标志当前是一个配置类。也就是说我们的主启动程序,也是一个配置类,不过是SpringBoot的一个核心配置类。

@ComponentScan

1
2
3
4
5
6
//SpringBoot配置类
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

接着我们在来看一下@ComponentScan:主要就是定义扫描的路径从中找出标识了需要装配的类自动装配到spring的bean容器中。

不过这里指定了那些类型和类不扫描,也就是过滤掉。

1
2
3
4
5
6
7
//SpringBoot配置类
@SpringBootConfiguration
@EnableAutoConfiguration
//组件扫描自动装配到IOC容器
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

@EnableAutoConfiguration

接下来只剩下@EnableAutoConfiguration注解了。这个就是自动配置的核心注解。我们查看源码

image-20201225153752376

1
2
3
4
5
6
7
8
9
10
11
12
13
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";


Class<?>[] exclude() default {};


String[] excludeName() default {};

}

发现有两个注解@AutoConfigurationPackage@Import

@AutoConfigurationPackage

查看@AutoConfigurationPackage源码。

image-20201225154015566

1
2
3
4
5
6
7
8
9
//导入AutoConfigurationPackages.Registrar对象
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

String[] basePackages() default {};

Class<?>[] basePackageClasses() default {};

}

发现该类通过@Import导入了AutoConfigurationPackages.Registrar组件。我们继续点进去查看源码

image-20201225154237417

1
2
3
4
5
6
7
8
9
10
11
12
13
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}

@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}

}

发现该类只有两个方法registerBeanDefinitions,determineImports.我们在第一个方法中打一个断点,debug查看。

image-20201225154808593

AnnotationMetadata的信息,来自标注@SpringBootApplication(合成注解)的主启动类上。

image-20201225155129503

接着通过AnnotationMetadata或者注解标注类所在的包信息转换成一个数组。然后register注册到IOC容器中。

也就是说@AutoConfigurationPackage注解通过利用Registrar导入注册主程序所在包下所有添加@Component,@Controller….的类到IOC容器中。

这也就就解释了为啥我们在主程序包下或者子包编写配置类,Cotroller…能被自动扫描注册到容器中。

@Import

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//导入注册主程序包下的组件
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";


Class<?>[] exclude() default {};


String[] excludeName() default {};

}

我们在来看看@Import(AutoConfigurationImportSelector.class),@Import注解的作用很简单,就是导入注册一个组件到容器中。所以关键是AutoConfigurationImportSelector.class

image-20201225163349951

1
2
3
4
5
6
7
8
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

可以看出这里的核心是getAutoConfigurationEntry(annotationMetadata);方法

image-20201225163444713

image-20201225163647363

List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);获取到的configurations的size是130。

image-20201225163745759

这130个组件是要导入到容器中的,它是哪里获取的呢??我们继续查看getCandidateConfigurations

image-20201225164047156

1
2
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());

我们继续查看loadFactoryNames方法

image-20201225164313300

我们继续查看loadSpringFactories方法

image-20201225164429108

打个断点,debug执行。

image-20201225164603247

我们可以得知它是加载FACTORIES_RESOURCE_LOCATION这个属性, 默认扫描我们当前系统里面所有META-INF/spring.factories位置文件加载组件的。

而自动配置最核心的也就是spring-boot-autoconfigure-2.4.1.jar。我们点开该jar包发现META-INF/spring.factories文件查看。

image-20201225164918363

image-20201225165129290

151-22+1=130正好是我们先前看到的加载的组件个数130个。文件里面写死了spring-boot一启动就要给容器中加载的所有配置类

按需开启自动配置项

虽然我们130个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration都是都按照条件装配规则@Conditional,最终会按需配置。

例如我们查看文件中的RabbitAutoConfiguration

image-20201225165718173

@ConditionalOnClass({ RabbitTemplate.class, Channel.class })不存在RabbitTemplate,Channel类所以以下配置都不生效。

用户自定义优先

SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先。例如WebMvcAutoConfiguration

image-20201225170624568

@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)当容器中没有WebMvcConfigurationSupport类型的组件时配置才生效。

总结

  • SpringBoot会扫描所有META-INF/spring.factories位置文件加载组件。而自动配置最核心的也就是spring-boot-autoconfigure-2.4.1.jar。所以加载的是spring-boot-autoconfigure-2.4.1.jarMETA-INF/spring.factories文件中写好的自动配置类(xxxxxAutoConfiguration)。

  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定

  • 生效的配置类就会给容器中装配很多组件

  • 只要容器中有这些组件,相当于这些功能就有了

定制化配置

  1. 用户直接自己@Bean替换底层的组件

  2. 用户去看这个组件是获取的配置文件什么值就去修改。(配置文件配置)

spring-boot-autoconfigure.jar —> META-INF/spring.factories —>xxxxxAutoConfiguration —> 组件 —>xxxxProperties里面拿值 —-> application.properties