今天学习了SpringBoot2的自动配置原理,认为十分巧妙,所以记录一下自己的学习感悟。
1 预备知识
在介绍SpringBoot2的自动装配原理之前,有几个注解需要我们认识一下,这几个注解在我看来是自动装配原理的基础。
1.1 @Conditional 条件装配
这里的注解并不只有@Conditional,而是和@Conditional具有相同功能的注解,比如以下注解:
此类注解的作用是:当满足Conditional指定的条件时,才进行组件的注入。
1.2 @Import
向容器中导入指定类型的组件
2 自动配置原理
2.1 自动加载配置类
进入SpringBoot2的启动类可以看到三个比较重要的注解,如下:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
)
其中,@SpringBootConfiguration标注这是一个配置类,@ComPonentScan即要扫描那些包。这三个中最重要的即是@EnableAutoConfiguration注解。源码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
====以下较为重要======
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
@AutoConfigurationPackager
重要部分源码如下:
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
其中@Import({Registrar.class})给容器中导入Registrar类型的组件。
进入Register中可以看到以下方法
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
其中new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames()作用是得到主类的包名,然后将其封装到一个数组中,之后register扫描此包下所有符合条件的组件,再注册进容器中。
所以说@AutoConfigurationPackager注解,作用是将指定的一个包下的组件导入容器中,默认为启动类所在的包下,这也说明了在没有修改相关配置时,我们要将其他组件写在启动类的目录下。
@Import({AutoConfigurationImportSelector.class})
此注解导入了AutoConfigurationImportSelector类型的组件,进入AutoConfigurationImportSelector代码中可以看到下面的方法:
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
在List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes)打断点可以得到:
由此可见,此方法是用来获取需要导入到容器的组件。再进入此方法可以得到最关键的方法,名为loadSpringFactories,源码太多,就不全部贴了。我们可以在loadSpringFactories的源码中看到此语句Enumeration urls = classLoader.getResources("META-INF/spring.factories") 目的是加载META-INF/spring.factories文件,默认扫描我们当前系统中所有META-INF/spring.factories文件。
且spring-boot-autoconfigure-2.6.1.jar包中也有META-INF/spring.factories,此文件中写死了SpringBoot一启动就要给容器中加载的所有配置类,
2.2 按需开启自动配置项
上面说了META-INF/spring.factories文件中的所有场景的自动配置在启动的时候默认全部加载,但最终配置时并没有那么多,因为在配置时会按需配置,有些组件并不会配置。
在SpringBoot2中,按需配置的解决方法主要是使用@Conditional及其有相同功能的注解
下面举个例子:
例如SpringBoot2中的Aop中使用了以下注解
@ConditionalOnMissingClass({"org.aspectj.weaver.Advice"})
即有Advice类才会把Aop放入容器中。
3 SpringBoot2的自动装配流程
即启动时加载META-INF/spring.factories中所有的配置,但根据@Conditional及其有相同功能的注解使它们并不能全部生效,这样来保证按需装配。
这样来看,如果不被成千上万条源码吓到的话,SpringBoot2的自动装配原理挺简单的,加油。学习无止境。