Spring Boot 自动装配原理 / 3
在上一篇内容中,我们了解了关于Spring Boot在初始化过程和启动准备过程中,主类被加载进Bean容器的过程,以及
spring.factories
文件的加载过程及内容,还没有看的同学可以去看一看,链接在这里。本节将会针对@EnableAutoConfiguration
的解析过程,以及spring.factories
内自动装配相关类是如何进行过滤的,本篇由于堆栈过程较多,整体节奏会比较快,也建议大家能打开编译器下载好源码按照文字中的顺序进行阅读。
主类的注解解析过程
在上一节中,我们的Spring Boot应用已经顺利完成了初始化阶段、准备阶段的工作内容,正式进入了启动阶段。接下来就需要面对本系列内容面对的核心问题:”Spring是在何时进行自动装配注解的解析的,以及自动装配类是再何时、如何进行加载的。”
我们都知道,Spring的一大优势是对项目内的对象进行统一的生命周期管理以达成控制反转的目的,而BeanFactory
则负责了整体的对象管理的工作。在第一篇内容中我们提到在Spring应用启动阶段,即ApplicationContext.refresh()
方法内,会调用BeanFactory
的后处理器,以执行Spring Bean的解析、注册操作。
因为Spring的Bean注册解析堆栈太深,关于BeanFactory
的后置处理工作就不于此贴代码了,想具体了解的同学可以以AbscractApplicationContext#refresh()
-> invokeBeanFactoryPostProcessors()
-> PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()
-> invokeBeanDefinitionRegistryPostProcessors()
为主要调用链路进行代码的阅读,在文章内就只拣重点的部分来看了。
配置类的后置处理器
在org.springframework.context.support.PostProcessorRegistrationDelegate
类的invokeBeanFactoryPostProcessors()
方法中,BeanFactory
会通过getBeanNamesForType
获取BeanDefinitionRegistryPostProcessor
实现类。
1 | String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); |
于此处我们会获得一个实现类ConfigurationClassPostProcessor
,该类会负责解析注解了@Configuration
的Bean。那么这个类的来源是在哪呢?这里我们就要回头再去看看Spring应用再初始化阶段推断、构建上下文的过程。在通过应用类型构建对应的Spring上下文之后会通过类全限定名获取类信息,而后通过反射进行类构建,但无论是Servlet
应用还是Reactive
应用,抑或是非web应用,其上下文类构造方法里我们都能看见此段代码this.reader = new AnnotatedBeanDefinitionReader(this)
,在AnnotatedBeanDefinitionReader
的构造函数中,我们能看到该类会向BeanFactory
中注册一些相关的后置处理器,其中就包括了ConfigurationClassPostProcessor
。
1 | if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { |
在我们明确了具体的具体的后置处理器之后,就可以跟进到ConfigurationClassPostProcessor#processConfigBeanDefinitions()
方法内部去看进一步的解析情况了。
ConfigurationClassParser
在processConfigBeanDefinitions()
方法的内部逻比较明确,我会在下面贴出部分代码,省略掉中间一些并不是太重要的内容并用注释描述部分逻辑做用。
1 | public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { |
进入到类的解析过程,已经里我们的目标越来越近了。该过程将会解析类的@PropertySource
、@ComponentScan
、@Import
、@ImportResource
、@Bean
几个注解。在第一篇内容中我们提到了@EnableAutoConfiguration
的元注解@Import(AutoConfigurationImportSelector.class)
在这终于派上了用场。我们暂且忽略其它的注解处理逻辑,只关注相关的@Import
部分。
1 | /** |
在procressImports()
方法内部,我们也只需要关注其中一部分:
1 | for (SourceClass candidate : importCandidates) { |
在解析过程结束后,解析器就会通过this.deferredImportSelectorHandler.process()
,进行解析过程中设定的延迟处理操作。
1 | public void process() { |
自动装配的处理过程
跟进handler#processGroupImports()
方法,在该方法内部,会通过调用ImprotSelecot
的process()
方法获取需要导入的定义列表。因为@EnableAutoConfiguration
导入的类是AutoConfigurationImportSelector
,我们可以从此类的AutoConfigurationGroup#procress()
方法作为起始点进行分析。
1 | public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { |
spring-autoconfigure-metadata.properties
在process()
方法内主要有两个要点需要注意,第一部分是getAutoConfigurationMetadata()
方法,该方法会通过类加载器扫描类路径下的META-INF/spring-autoconfigure-metadata.properties
,这个文件记录了关于自动装配类的一些元信息,我们可以先看一下这个文件的内容结构,具体的做用过会儿我们再说。
1 | org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration.ConditionalOnClass=org.apache.solr.client.solrj.SolrClient,org.springframework.data.solr.repository.SolrRepository |
文件内容结构可以总结为:[类全限定名].[条件]=[条件值]
筛选自动装配类列表
process()
方法内的第二个要点就是getAutoConfigurationEntry()
方法的内部逻辑,该部分也是Spring Boot自动装配的核心之所在。
1 | protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, |
除了从spring.factories
获取到自动配置类列表之外,对类定义进行过滤操作也是十分重要的一环,我们需要排除掉自动配置条件不成立的自动配置类,只处理条件成立的那些自动装配类。这部分也是由于堆栈过多于此不再详细描述,就把重点的部分拎出来说一说。
filter()
方法的内部逻辑是会从spring.factories
文件内容(缓存)中获取到AutoConfigurationImportFilter
相关的过滤类,默认的会有OnBeanCondition
、OnClassCondition
、OnWebApplicationCondition
三种过滤器(可在spring-boot-autoconfigure-2.2.5.RELEASE.jar!META-INF/spring.factories
找到这三个类的配置信息),当然我们也可以根据这种规则添加自己的过滤器(tip: 可在自定义过滤器中排除掉一些项目不需要的自动装配类,以减少自动装配类的解析时间,也是一种提升Spring Boot应用启动速度的小技巧)。通过spring.factories
里获取到的类全限定名,拼接过滤方式,以在元信息列表里获取到过滤条件,而过滤器则会按照各自的规则进行判断。
- 过滤方式
OnClassCondition
- 获取到过滤条件,即需要存在的类全限定名之后。通过类加载器对类进行解析,若解析失败,抛出类不存在异常,则类不存在,反之则存在。
OnWebApplicationCondition
- 获取到过滤条件,即需要对应的应用类型之后。通过对应用类型名称作对比,以及类类型所对应关键类的加载进行验证。若两者比对通过,则条件成立,反之则不成立
OnBeanCondition
OnBeanCondition
的条件判定方式与onClassCondition
类似,也是通过类加载器对类进行解析,若需求的类全部都存在则条件成立,反之则不成立。(这一块会留下一个疑惑,为什么判断的是类是否在类加载器中存在而不是在BeanFactory
中存在呢?)
通过以上三种过滤方式对自动装配类列表进行第一次过滤,我们将spring.factories
中大部分不需要的自动装配类已经被过滤掉了。之后就需要对类进行细粒度的解析,在processGroupImports()
方法中,我们可以看到这么一行代码:
1 | processImports(configurationClass, |
最终迎来的自动装配
procressImports()
方法,是不是很熟悉。在上边我们提到的控制类解析过程中出现过它,Spring会对经过第一次筛选的配置类进行二次条件验证,即通过封装过的类定义获取到标注的条件信息,譬如@ConditionalOnMissingBean
、@ConditionalOnProperty
以及其它一些自定义条件注解,关于@Conditional
这方面的内容可能会在之后用一篇构建如何一个自定义Starter的过程中进行分析。在所有条件均判定通过后,shouldSkip()
方法会返回false,之后则会进入配置类的解析过程,其中就包括@ComponentScan
的扫描,@Bean
的注册解析等等,解析的内容前面也提到了,于此就不再赘述了,而我们的Spring Boot 自动装配过程,也与此完成了它的全部流程。
小结
从第一篇开始到现在,总算是把Spring Boot的自动装配流程梳理清楚,于此我再大致用链的方式梳理一遍。
注解于主类之上的@SpringBootApplication
-> 将主类注册进容器 -> 将spring.factories
写入缓存 -> 配置类的注解解析 -> @Import
注解的注入逻辑 -> 从spring.factories
获取自动装配类列表 -> 进行第一次粗粒度过滤 -> 进行第二次精确过滤 -> 解析自动装配类实现自动装配过程。
至此关于自动装配的过程就已经结束了,关于这这部分的内容大致我自觉已经讲清楚了,但是关于装配过程中的@Conditional
注解判断可能之后还会拎出来一篇自动装配原理Plus配合构建自定义Starter进行一个收尾,既然的之后的事情那就之后再说把。关于内容的问题或者说有什么建议也欢迎直接联系我,我的邮箱是:Loverconcerto@outlook.com,个人微信可以搜索:Qt1491717547。总之,生活还要继续。加油。