自动配置原理
# 100.自动配置原理
之前几篇,我们讲了 SpringBoot 的很多底层注解,现在就可以开始将自动配置原理了。
我们来看 SpringBoot 到底怎么神不知鬼不觉帮我们做了那么多事情,让我们只需关注业务逻辑
我们从注解 @SpringBootApplication
开始
# @SpringBootApplication
我们之前在主程序中,加上该注解,就能完成项目的启动:
package com.peterjxl.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 主程序类
* @SpringBootApplication 这是一个 SpringBoot 应用程序
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
我们可以看该注解的源码,
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
}
)
public @interface SpringBootApplication{}
//...........
2
3
4
5
6
7
8
9
其实可以看到其有 3 个关键的注解:
- @SpringBootConfiguration
- @EnableAutoConfiguration(核心)
- @ComponentScan
我们挨个分析
# @SpringBootConfiguration
我们可以看其源码,其实就是一个@Configuration,代表当前是一个配置类
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
2
3
4
5
6
7
8
9
10
所以,我们 SpringBoot 项目的 main 程序,其实也是一个配置类。该注解比较简单
# @ComponentScan
该注解其实就是一个包扫描的路径,其实是 Spring 的注解
# @EnableAutoConfiguration
我们看其源码,其实也是一个合成注解:
//........
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
//........
2
3
4
5
总得来说,该注解的作用就是:
- 扫描 SpringBoot 主程序下,所有子包的类,加载对应的组件到容器中
- 导入自动配置类,该类会引入有 starter 的自动配置类,然后根据条件装配注解,决定哪些配置生效,哪些不生效。
# @AutoConfigurationPackage
自动配置包。其源码:
@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage {
2
也就是说,该注解就是用来导入一个组件 Registrar,利用 Registrar 给容器中导入一系列组件,将指定的一个包下的所有组件导入进来(也就是 MainApplication 所在包下的子包):源码如下
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
2
3
4
5
6
7
8
9
10
11
12
解读:
- 第 5 行:方法的参数,有 metadata,也就是注解的元信息,也就是 SpringBoot 的主程序上的那个注解的信息,里面包含了包名
- 第 6 行:获取到元信息,然后获取所有子包的信息(并作为一个数组)
- 下一步,就是批量注册组件了
这也就是为什么,SpringBoot 默认会扫描主程序下所有子包,并注册组件了。
# @Import({AutoConfigurationImportSelector.class})
该注解,就是使用 Selector 来决定导入什么组件。首先,该注解有个方法:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
2
3
4
5
6
7
8
解读:该方法决定了要导入哪些组件(第 5 行调用其他方法),然后转成一个数组(toStringArray 方法)
我们看看 getAutoConfigurationEntry 方法:
protected 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 AutoConfigurationEntry(configurations, exclusions);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
解读:关键是第 6 行,configurations 列表里,存储了准备导入到容器中的组件,在这里是 127 个(读者可以自己 debug)
接下来我们看看 getCandidateConfigurations 方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
2
3
4
5
解读:利用工厂(SpringFactoriesLoader)加载 Map<String, List<String>>
loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
然后我们看看 loadFactoryNames 方法:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
2
3
4
而第三行的 loadSpringFactories 方法,就是会返回一个 Map。
loadSpringFactories 源码:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
MultiValueMap<String, String> result = new LinkedMultiValueMap();
//...........
2
3
4
5
6
7
8
9
可以看到,其实是读取了一个配置文件:META-INF/spring.factories。
也就是说,会默认扫描我们当前系统里面所有 jar 包中,META-INF/spring.factories 文件。有的 jar 包没有该文件,而 spring-boot-autoconfigure-2.3.4.RELEASE.jar 包有!
该文件里面写死了 SpringBoot 一启动就要给容器中加载的所有配置类,共 127 个类:
spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,
..............
2
3
4
5
6
7
8
9
10
11
12
13
14
但为什么我们之前打印容器中的所有组件,好像没有这这些自动配置类?
虽然都引入了,但是至于哪些配置生效,哪些不生效,就是下一节要讲的内容了:按需开启自动配置项
总结:
- 利用 getAutoConfigurationEntry(annotationMetadata); 给容器中批量导入一些组件
- 调用
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)
获取到所有需要导入到容器中的配置类 - 利用工厂加载
Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);
得到所有的组件 - 从 META-INF/spring.factories 位置来加载一个文件。默认扫描我们当前系统里面所有 META-INF/spring.factories 位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar 包里面也有 META-INF/spring.factories
# 按需开启自动配置项
虽然我们 127 个场景的所有自动配置启动的时候默认全部加载:xxxxAutoConfiguration
但根据条件装配规则(@Conditional),最终会按需配置,例如我们查看某个 xxxxAutoConfiguration:
package org.springframework.boot.autoconfigure.aop;
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {
//.....................
2
3
4
5
6
7
8
9
10
有个注解:@ConditionalOnClass(Advice.class)
,有了该类,也就是导入相关的 starter 的时候,才有这个类,然后该配置类才会生效