SpringBoot-AutoConfiguration 自动装配
大家都知道,SpringBoot自动装配的主要角色就是ConfigurationClassPostProcessor,在分析它之前,先看看它是怎么被注入和被调用的。
ConfigurationClassPostProcessor的由来
在SpringApplication.run方法的context = createApplicationContext();中,可以看到拿到的类名是DEFAULT_SERVLET_WEB_CONTEXT_CLASS)=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext,然后创建了该类的对象
1 | // AnnotationServletContext 构造方法 |
创建的Context中包含了AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner。
1 | // AnnotatedReader 构造方法 |
该Reader干了解析Annotation非常重要的两件事,一个是创建ConditionEvaluator,主要是用来解析和@Conditional相关的(Internal class used to evaluate {@link Conditional} annotations.);另外一个就是注册所有和Annotation相关的PostProcessor。
1 | // AnnotationConfigUtils.registerAnnotationConfigProcessors |
当运行完prepareEnvrionment方法之后,BeanFactory中装载了主函数所在的类,此时所有的bean就是 为Annotation准备提前注入的Bean和主运行程序Bean。
至此,Annotation相关的PostProcessor已经准备好了,那么spring是怎么完成自动装配的呢?
ConfigurationClassPostProcessor源码分析
在调用AbstractApplicationContext.refresh的时候,会调用BeanDefinitionRegistryPostProcessor。所以此时就会调用ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry:
1 | public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { |
parse
下面详细的研究下parse的流程:
1 | public void parse(Set<BeanDefinitionHolder> configCandidates) { |
ClassPathBeanDefinitionScanner.doScann 根据@ComponentScan创建BeanDefinition的过程
1 |
|
AnnotationConfigUtils.processCommonDefinitionAnnotations 根据Bean的相关注解完善BeanDefinition的定义1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));
}
else if (abd.getMetadata() != metadata) {
lazy = attributesFor(abd.getMetadata(), Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));
}
}
if (metadata.isAnnotated(Primary.class.getName())) {
abd.setPrimary(true);
}
AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
if (dependsOn != null) {
abd.setDependsOn(dependsOn.getStringArray("value"));
}
AnnotationAttributes role = attributesFor(metadata, Role.class);
if (role != null) {
abd.setRole(role.getNumber("value").intValue());
}
AnnotationAttributes description = attributesFor(metadata, Description.class);
if (description != null) {
abd.setDescription(description.getString("value"));
}
}
@Import注解的解析
1 | private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, |
selector 分为三种,
- 作为
ImportSelector也就是Importer,用来引入其他类的。这种情况又做两种处理,延迟不延迟。 - 作为
ImportBeanDefinitionRegistrar,通过import该类,来注册某些Bean - 都不是的话,被当作
Configuration配置类来处理
放在deferredImportSelectorHandler需要延迟处理的selector是在整个configClass解析完了之后才调用其中的方法的。
如果到这里解析完这个configClass,那么就会返回,如果它的父类为null,那么整个parse方法也会返回,最后调用deferprocess
1 | // 这是最开始的parse方法,最后调用了defer.process |
处理AutoConfigurationImportSelector
1 | public void process() { |
下面仔细看看 getImports方法
1 | public Iterable<Group.Entry> getImports() { |
流程总结
看了这么多,parse的过程可能稍微有点蒙(博主自己至少跟了3遍源码),那么最后捋一下整个流程:
parse开始,也就是processorConfigurationClass,最开始默认的被解析类只有我们的启动配置类AppTestApplication- 先看是否跳过
shuoldSkip,然后包装AppTestApplication为SourceClass和ConfigClass(这两个类贯穿了整个AutoConfig流程) - 然后真正进入解析–
doProcessConfigurationClass。这里需要注意该方法返回的还是一个SourceClass对象,就是当前ConfigClass包装的类的父类。- 解析注解
@Component, @PropertySources - 解析注解
@ComponentScans,这时候拿到了我们工程中配置的,被这些注解@Configuration, @Controller, @Service修饰的类。- 对这些类执行
parse
- 对这些类执行
- 解析注解
@Import这里需要检查循环import的问题- 拿到当前配置类上所有
@Import引入的类,包括注解类上的注解引入的。 processImports. 被Import进来的一共分为三种:- 作为
ImportSelector也就是Importer,用来引入其他类的。这种情况又做两种处理,延迟不延迟。- 是
DeferredImportSelector的子类(AutoConfigurationImportSelector就是它的字类),放到deferredHandler延迟处理 - 直接扫描该类上的
@Import,并执行processImport
- 是
- 作为
ImportBeanDefinitionRegistrar,通过import该类,来注册某些Bean - 都不是的话,被当作
Configuration配置类来处理,回到了最初的processorConfigurationClass
- 作为
- 拿到当前配置类上所有
- 解析注解
@ImportResource, @Bean等其他的。。。
- 解析注解
doProcessConfigurationClass返回ConfigClass包装的类的父类,继续doProcessConfigurationClass
- 先看是否跳过
- 当前类的
parse的最后,就是执行完processorConfigurationClass回来之后,再去处理之前放在deferredHandler中需要延迟处理的ImportSelector.deferredImportSelectorHandler.process()- 处理
DeferredImports,获取该Import想要import的类的列表。(AutoConfigurationImportSelector引入了一大堆配置类) - 对这些配置类执行
processImports。
- 处理
自动装配之条件装配
Condition
@Conditional是Spring提供的核心注解之一,通常可以加在类或方法上,配合@Configuration和@Bean使用,当和@Configuration配合使用时,那么该类下所有@Bean方法 或者@Import 或者 @ComponentScan都会受到其配置条件的影响
@Conditional
1 | ({ElementType.TYPE, ElementType.METHOD}) |
这个注解只有一个属性,值是Condition类型的。那么再看看Condition
Condition
1 |
|
Condition是真正检测、匹配被@Conditional注解的类是否满足@Conditional中value属性配置的Condition中的条件。
简单来说,就是 @Conditional来引入条件判断的裁判Condition,真正判断是否match,就看Condition了。
match方法中有两个重要的参数,ConditionContext context能够帮忙获取spring中所有重要的环境变量,比如BeanFactory, ResourceLoader, Environment等, AnnotatedTypeMetadata metadata是获取被注解类上注解信息的。
ConfigurationCondition和SpringBootCondition都是Conditon的子类,也是Condition系列中比较重要的两个类。
ConfigurationCondition提供了更加灵活的控制,它多添加了一个用于设置解析Condition阶段的方法,在这里有两个阶段进行解析:
- PARSE_CONFIGURATION:会在解析@Configuration时进行condition的过滤
- REGISTER_BEAN:会在注册Bean的时候进行condition的过滤
SpringBootCondition 实现了Condition 而且SpringBoot中,很多Condition都继承子该类。
我们在自己写Condition的时候,实现哪个接口都可以。
那么我们常用的有ConditionalOnClass,ConditionalOnMissingClass,ConditionalOnBean,ConditionalOnMissingBean等等,比如还有以property作为条件,resource和profile作为条件的。
ConditionalOnBen在使用的时候,需要注意装配顺序的问题, 可能因为装配顺序的原因,达不到预期的效果.
·
ConfigurationClassBeanDefinitionReader读取启动类上各种配置类中声明需要加载的beanConfigurationClassPostProcessor 就是读取自动配置引入类的processor
ConfigurationClassParser.doProcessConfigurationClass 真正解析@Configuration注解的类上的各种其他标签。ComponentScanAnnotationParser.parse 真正解析 @ComponentScan注解标记需要扫描的类
spring 通过类ClassReader来解析.class文件,最后获取class的各种信息,包括Annotation信息(可是为什么不根据classLoader去获取该class的各种信息呢)。可以看看SimpleMetadataReader
spring-autoconfigure-metadata.properties
之前分析源码的时候,在过滤从spring.factories读取的所有EnableAutoConfiguration需要用到AutoConfigurationMetadata,是通过AutoConfigurationImportSelector.getAutoConfigurationMetadata来获取的:
1 | private AutoConfigurationMetadata getAutoConfigurationMetadata() { |
AutoConfigurationMetadata就是从META-INF/spring-autoconfigure-metadata.properties读取的。
怎么使用呢?比如说spring.factories中配置了
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
spring-autoconfigure-metadata.properties配置了
1 | com.ys.zhshop.member.config.MemberAutoConfiguration.ConditionalOnClass=java.lang.String |
基本格式就是自动配置的类全名.条件=值,上面条件满足了,所以就会加在这个配置类。