1 @SpringBootApplication注解
在开始前,我们先想一下,SpringBoot为何一个标注有@SpringBootApplication
注解的启动类通过执行一个简单的run
方法就能实现SpringBoot大量Starter
的自动配置呢? 其实SpringBoot的自动配置就跟@SpringBootApplication
这个注解有关,我们先来看下其这个注解的源码:
1 | (ElementType.TYPE) |
@SpringBootApplication
标注了很多注解,我们可以看到其中跟SpringBoot自动配置有关的注解就有一个即@EnableAutoConfiguration
,因此,可以肯定的是SpringBoot的自动配置肯定跟@EnableAutoConfiguration
息息相关(其中@ComponentScan
注解的excludeFilters
属性也有一个类AutoConfigurationExcludeFilter
,这个类跟自动配置也有点关系,但不是我们关注的重点)。 现在我们来打开@EnableAutoConfiguration
注解的源码:
1 | (ElementType.TYPE) |
看到@EnableAutoConfiguration
注解又标有@AutoConfigurationPackage
和@Import(AutoConfigurationImportSelector.class)
两个注解,顾名思义,@AutoConfigurationPackage
注解肯定跟自动配置的包有关,而AutoConfigurationImportSelector
则是跟SpringBoot的自动配置选择导入有关(Spring中的ImportSelector
是用来导入配置类的,通常是基于某些条件注解@ConditionalOnXxxx
来决定是否导入某个配置类)。
因此,可以看出AutoConfigurationImportSelector
类是我们本篇的重点,因为SpringBoot的自动配置肯定有一个配置类,而这个配置类的导入则需要靠AutoConfigurationImportSelector
这个哥们来实现。
接下来我们重点来看AutoConfigurationImportSelector
这个类,完了我们再简单分析下@AutoConfigurationPackage
这个注解的逻辑。
2 如何去找SpringBoot自动配置实现逻辑的入口方法?
可以肯定的是SpringBoot的自动配置的逻辑肯定与AutoConfigurationImportSelector这个类有关,那么我们该如何去找到SpringBoot自动配置实现逻辑的入口方法呢?
在找SpringBoot自动配置实现逻辑的入口方法前,我们先来看下AutoConfigurationImportSelector
的相关类图,好有个整体的理解。看下图:
图1可以看到AutoConfigurationImportSelector
重点是实现了DeferredImportSelector
接口和各种Aware
接口,然后DeferredImportSelector
接口又继承了ImportSelector
接口。
自然而然的,我们会去关注AutoConfigurationImportSelector
复写DeferredImportSelector
接口的实现方法selectImports
方法,因为selectImports
方法跟导入自动配置类有关,而这个方法往往是程序执行的入口方法。经过调试发现selectImports
方法很具有迷惑性,selectImports
方法跟自动配置相关的逻辑有点关系,但实质关系不大。
此时剧情的发展好像不太符合常理,此时我们又该如何来找到自动配置逻辑有关的入口方法呢?
最简单的方法就是在AutoConfigurationImportSelector
类的每个方法都打上断点,然后调试看先执行到哪个方法。但是我们可以不这么做,我们回想下,自定义一个Starter
的时候我们是不是要在spring.factories
配置文件中配置
1 | EnableAutoConfiguration=XxxAutoConfiguration |
因此可以推断,SpringBoot的自动配置原理肯定跟从spring.factories
配置文件中加载自动配置类有关,于是结合AutoConfigurationImportSelector
的方法注释,我们找到了getAutoConfigurationEntry
方法。于是我们在这个方法里面打上一个断点,此时通过调用栈帧来看下更上层的入口方法在哪里,然后我们再从跟自动配置相关的更上层的入口方法开始分析。
通过图2我们可以看到,跟自动配置逻辑相关的入口方法在DeferredImportSelectorGrouping
类的getImports
方法处,因此我们就从DeferredImportSelectorGrouping
类的getImports
方法来开始分析SpringBoot的自动配置源码好了。
3 分析SpringBoot自动配置原理
既然找到ConfigurationClassParser.getImports()方法
是自动配置相关的入口方法,那么下面我们就来真正分析SpringBoot自动配置的源码了。
先看一下getImports
方法代码:
1 | // ConfigurationClassParser.java |
标【1】
处的的代码是我们分析的重中之重,自动配置的相关的绝大部分逻辑全在这里了,将在3.1 分析自动配置的主要逻辑深入分析。那么this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector())
;主要做的事情就是在this.group
即AutoConfigurationGroup
对象的process
方法中,传入的AutoConfigurationImportSelector
对象来选择一些符合条件的自动配置类,过滤掉一些不符合条件的自动配置类,就是这么个事情,无他。
注:
AutoConfigurationGroup
:是AutoConfigurationImportSelector
的内部类,主要用来处理自动配置相关的逻辑,拥有process
和selectImports
方法,然后拥有entries
和autoConfigurationEntries
集合属性,这两个集合分别存储被处理后的符合条件的自动配置类,我们知道这些就足够了;AutoConfigurationImportSelector
:承担自动配置的绝大部分逻辑,负责选择一些符合条件的自动配置类;metadata
:标注在SpringBoot启动类上的@SpringBootApplication
注解元数据
标【2】
的this.group.selectImports
的方法主要是针对前面的process
方法处理后的自动配置类再进一步有选择的选择导入,将在3.2 有选择的导入自动配置类这小节深入分析。
3.1 分析自动配置的主要逻辑
这里继续深究前面 4 分析SpringBoot自动配置原理这节标【1】
处的 this.group.process
方法是如何处理自动配置相关逻辑的。
1 | // AutoConfigurationImportSelector$AutoConfigurationGroup.java |
上面代码中我们再来看标【1】
的方法getAutoConfigurationEntry
,这个方法主要是用来获取自动配置类有关,承担了自动配置的主要逻辑。直接上代码:
1 | // AutoConfigurationImportSelector.java |
AutoConfigurationEntry
方法主要做的事情就是获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费。我们下面总结下AutoConfigurationEntry
方法主要做的事情:
【1】从spring.factories
配置文件中加载EnableAutoConfiguration
自动配置类(注意此时是从缓存中拿到的哈),获取的自动配置类如图3所示。这里我们知道该方法做了什么事情就行了,后面还会有一篇文章详述spring.factories
的原理;
【2】若@EnableAutoConfiguration
等注解标有要exclude
的自动配置类,那么再将这个自动配置类排除掉;
【3】排除掉要exclude
的自动配置类后,然后再调用filter
方法进行进一步的过滤,再次排除一些不符合条件的自动配置类;这个在稍后会详细分析。
【4】经过重重过滤后,此时再触发AutoConfigurationImportEvent
事件,告诉ConditionEvaluationReport
条件评估报告器对象来记录符合条件的自动配置类;(这个在5 AutoConfigurationImportListener这小节详细分析。)
【5】 最后再将符合条件的自动配置类返回。
"图3
总结了AutoConfigurationEntry
方法主要的逻辑后,我们再来细看一下AutoConfigurationImportSelector
的filter
方法:
1 | // AutoConfigurationImportSelector.java |
AutoConfigurationImportSelector
的filter
方法主要做的事情就是调用AutoConfigurationImportFilter
接口的match
方法来判断每一个自动配置类上的条件注解(若有的话)@ConditionalOnClass
,@ConditionalOnBean
或@ConditionalOnWebApplication
是否满足条件,若满足,则返回true,说明匹配,若不满足,则返回false说明不匹配。
我们现在知道AutoConfigurationImportSelector
的filter
方法主要做了什么事情就行了,现在先不用研究的过深,至于AutoConfigurationImportFilter
接口的match
方法将在5 AutoConfigurationImportFilter这小节再详细分析,填补一下我们前一篇条件注解源码分析中留下的坑。
注意:我们坚持主线优先的原则,其他枝节代码这里不深究,以免丢了主线哈。
3.2 有选择的导入自动配置类
这里继续深究前面 3 分析SpringBoot自动配置原理这节标【2】
处的 this.group.selectImports
方法是如何进一步有选择的导入自动配置类的。直接看代码:
1 | // AutoConfigurationImportSelector$AutoConfigurationGroup.java |
可以看到,selectImports
方法主要是针对经过排除掉exclude
的和被AutoConfigurationImportFilter
接口过滤后的满足条件的自动配置类再进一步排除exclude
的自动配置类,然后再排序。逻辑很简单,不再详述。
4 AutoConfigurationImportFilter
这里继续深究前面 4.1节的 AutoConfigurationImportSelector.filter
方法的过滤自动配置类的boolean[] match = filter.match(candidates, autoConfigurationMetadata);
这句代码。
因此我们继续分析AutoConfigurationImportFilter
接口,分析其match
方法,同时也是对前一篇@ConditionalOnXxx
的源码分析文章中留下的坑进行填补。
AutoConfigurationImportFilter
接口只有一个match
方法用来过滤不符合条件的自动配置类。
1 |
|
同样,在分析AutoConfigurationImportFilter
接口的match
方法前,我们先来看下其类关系图:
图4
可以看到,AutoConfigurationImportFilter
接口有一个具体的实现类FilteringSpringBootCondition
,FilteringSpringBootCondition
又有三个具体的子类:OnClassCondition
,OnBeanCondtition
和OnWebApplicationCondition
。
那么这几个类之间的关系是怎样的呢?
FilteringSpringBootCondition
实现了AutoConfigurationImportFilter
接口的match
方法,然后在FilteringSpringBootCondition
的match
方法调用getOutcomes
这个抽象模板方法返回自动配置类的匹配与否的信息。同时,最重要的是FilteringSpringBootCondition
的三个子类OnClassCondition
,OnBeanCondtition
和OnWebApplicationCondition
将会复写这个模板方法实现自己的匹配判断逻辑。
好了,AutoConfigurationImportFilter
接口的整体关系已经清楚了,现在我们再进入其具体实现类FilteringSpringBootCondition
的match
方法看看是其如何根据条件过滤自动配置类的。
1 | // FilteringSpringBootCondition.java |
FilteringSpringBootCondition
的match
方法主要做的事情还是调用抽象模板方法getOutcomes
来根据条件来过滤自动配置类,而复写getOutcomes
模板方法的有三个子类,这里不再一一分析,只挑选OnClassCondition
复写的getOutcomes
方法进行分析。
4.1 OnClassCondition
先直接上OnClassCondition
复写的getOutcomes
方法的代码:
1 | // OnClassCondition.java |
可以看到,OnClassCondition
的getOutcomes
方法主要解析自动配置类是否符合匹配条件,当然这个匹配条件指自动配置类上的注解@ConditionalOnClass
指定的类存不存在于classpath
中,存在则返回匹配,不存在则返回不匹配。
由于解析自动配置类是否匹配比较耗时,因此从上面代码中我们可以看到分别创建了firstHalfResolver
和secondHalfResolver
两个解析对象,这两个解析对象个分别对应一个线程去解析加载的自动配置类是否符合条件,最终将两个线程的解析自动配置类的匹配结果合并后返回。
那么自动配置类是否符合条件的解析判断过程又是怎样的呢?现在我们分别来看一下上面代码注释标注的【1】
,【2】
,【3】
和【4】
处。
4.1.1 createOutcomesResolver
这里对应前面4.1节的代码注释标注【1】
处的OutcomesResolver firstHalfResolver = createOutcomesResolver(...);
的方法:
1 | // OnClassCondition.java |
可以看到createOutcomesResolver
方法创建了一个封装了StandardOutcomesResolver
类的ThreadedOutcomesResolver
解析对象。 我们再来看下ThreadedOutcomesResolver
这个线程解析类封装StandardOutcomesResolver
这个对象的目的是什么?我们继续跟进代码:
1 | // OnClassCondtion.java |
可以看到在构造ThreadedOutcomesResolver
对象时候,原来是开启了一个线程,然后这个线程其实还是调用了刚传进来的StandardOutcomesResolver
对象的resolveOutcomes
方法去解析自动配置类。具体如何解析呢?稍后我们在分析【3】
处代码secondHalfResolver.resolveOutcomes();
的时候再深究。
4.1.2 new StandardOutcomesResolver
这里对应前面5.1节的【2】
处的代码OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(...);
,逻辑很简单,就是创建了一个StandardOutcomesResolver
对象,用于后面解析自动配置类是否匹配,同时,新建的一个线程也是利用它来完成自动配置类的解析的。
4.1.3 StandardOutcomesResolver.resolveOutcomes方法
这里对应前面5.1节标注的【3】
的代码ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
。
这里StandardOutcomesResolver.resolveOutcomes
方法承担了解析自动配置类匹配与否的全部逻辑,是我们要重点分析的方法,resolveOutcomes
方法最终把解析的自动配置类的结果赋给secondHalf
数组。那么它是如何解析自动配置类是否匹配条件的呢?
1 | // OnClassCondition$StandardOutcomesResolver.java |
可以看到StandardOutcomesResolver.resolveOutcomes
的方法中再次调用getOutcomes
方法,主要是从autoConfigurationMetadata
对象中获取到自动配置类上的注解@ConditionalOnClass
指定的类的全限定名,然后作为参数传入getOutcome
方法用于去类路径加载该类,若能加载到则说明注解@ConditionalOnClass
满足条件,此时说明自动配置类匹配成功。
但是别忘了,这里只是过了@ConditionalOnClass
注解这一关,若自动配置类还有其他注解比如@ConditionalOnBean
,若该@ConditionalOnBean
注解不满足条件的话,同样最终结果是不匹配的。这里扯的有点远,我们回到OnClassCondtion
的判断逻辑,继续进入getOutcome
方法看它是如何去判断@ConditionalOnClass
注解满不满足条件的。
1 | // OnClassCondition$StandardOutcomesResolver.java |
可以看到,getOutcome
方法再次调用重载方法getOutcome
进一步去判断注解@ConditionalOnClass
指定的类存不存在类路径中,跟着主线继续跟进去:
1 | // OnClassCondition$StandardOutcomesResolver.java |
我们一层一层的剥,最终剥到了最底层了,这个真的需要足够耐心,没办法,源码只能一点一点的啃,嘿嘿。可以看到最终是调用ClassNameFilter
的matches
方法来判断@ConditionalOnClass
指定的类存不存在类路径中,若不存在的话,则返回不匹配。
我们继续跟进ClassNameFilter
的源码:
1 | // FilteringSpringBootCondition.java |
可以看到ClassNameFilter
原来是FilteringSpringBootCondition
的一个内部枚举类,其实现了判断指定类是否存在于classpath
中的逻辑,这个类很简单,这里不再详述。
4.1.4 ThreadedOutcomesResolver.resolveOutcomes方法
这里对应前面5.1节的标注的【4】
的代码ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes()
。
前面分析5.1.3 StandardOutcomesResolver.resolveOutcomes方法已经刨根追底,陷入细节比较深,现在我们需要跳出来继续看前面标注的【4】
的代码ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes()
的方法哈。
这里是用新开启的线程去调用StandardOutcomesResolver.resolveOutcomes
方法解析另一半自动配置类是否匹配,因为是新线程,这里很可能会出现这么一种情况:主线程解析完属于自己解析的一半自动配置类后,那么久继续往下跑了,此时不会等待新开启的子线程的。
因此,为了让主线程解析完后,我们需要让主线程继续等待正在解析的子线程,直到子线程结束。那么我们继续跟进代码区看下ThreadedOutcomesResolver.resolveOutcomes
方法是怎样实现让主线程等待子线程的:
1 | // OnClassCondition$ThreadedOutcomesResolver.java |
可以看到用了Thread.join()
方法来让主线程等待正在解析自动配置类的子线程,这里应该也可以用CountDownLatch
来让主线程等待子线程结束。最终将子线程解析后的结果赋给firstHalf
数组。
4.2 OnBeanCondition和OnWebApplicationCondition
前面4.1 OnClassCondition节深入分析了OnClassCondition
是如何过滤自动配置类的,那么自动配置类除了要经过OnClassCondition
的过滤,还要经过OnBeanCondition
和OnWebApplicationCondition
这两个条件类的过滤,这里不再详述,有兴趣的小伙伴可自行分析。
5 AutoConfigurationImportListener
这里继续深究前面 4.1节的 AutoConfigurationImportSelector.getAutoConfigurationEntry
方法的触发自动配置类过滤完毕的事件fireAutoConfigurationImportEvents(configurations, exclusions);
这句代码。
我们直接点进fireAutoConfigurationImportEvents
方法看看其是如何触发事件的:
1 | // AutoConfigurationImportSelector.java |
如上,fireAutoConfigurationImportEvents
方法做了以下两件事情:
- 调用
getAutoConfigurationImportListeners
方法从spring.factoris
配置文件获取实现AutoConfigurationImportListener
接口的事件监听器;如下图,可以看到获取的是ConditionEvaluationReportAutoConfigurationImportListener
:
"
- 遍历获取的各个事件监听器,然后调用监听器各种
Aware
方法给监听器赋值,最后再依次回调事件监听器的onAutoConfigurationImportEvent
方法,执行监听事件的逻辑。
此时我们再来看下ConditionEvaluationReportAutoConfigurationImportListener
监听器监听到事件后,它的onAutoConfigurationImportEvent
方法究竟做了哪些事情:
1 | // ConditionEvaluationReportAutoConfigurationImportListener.java |
可以看到,ConditionEvaluationReportAutoConfigurationImportListener
监听器监听到事件后,做的事情很简单,只是分别记录下符合条件和被exclude
的自动配置类。
6 AutoConfigurationPackages
前面已经详述了SpringBoot的自动配置原理了,最后的最后,跟SpringBoot自动配置有关的注解@AutoConfigurationPackage
还没分析,我们来看下这个注解的源码:
1 | (ElementType.TYPE) |
可以看到@AutoConfigurationPackage
注解是跟SpringBoot自动配置所在的包相关的,即将 添加该注解的类所在的package 作为 自动配置package 进行管理。
接下来我们再看看AutoConfigurationPackages.Registrar
类是干嘛的,直接看源码:
1 | //AutoConfigurationPackages.Registrar.java |
可以看到Registrar
类是AutoConfigurationPackages
的静态内部类,实现了ImportBeanDefinitionRegistrar
和DeterminableImports
两个接口。现在我们主要来关注下Registrar
实现的registerBeanDefinitions
方法,顾名思义,这个方法是注册bean
定义的方法。看到它又调用了AutoConfigurationPackages
的register
方法,继续跟进源码:
1 | // AutoConfigurationPackages.java |
如上,可以看到register
方法注册了一个packageNames
即自动配置类注解@EnableAutoConfiguration
所在的所在的包名相关的bean
。那么注册这个bean
的目的是为了什么呢? 结合官网注释知道,注册这个自动配置包名相关的bean
是为了被其他地方引用,比如JPA entity scanner
,具体拿来干什么久不知道了,这里不再深究了。
7 小结
好了,SpringBoot的自动配置的源码分析就到这里了,比较长,有些地方也很深入细节,读完需要一定的耐心。
最后,我们再总结下SpringBoot自动配置的原理,主要做了以下事情:
- 从spring.factories配置文件中加载自动配置类;
- 加载的自动配置类中排除掉
@EnableAutoConfiguration
注解的exclude
属性指定的自动配置类; - 然后再用
AutoConfigurationImportFilter
接口去过滤自动配置类是否符合其标注注解(若有标注的话)@ConditionalOnClass
,@ConditionalOnBean
和@ConditionalOnWebApplication
的条件,若都符合的话则返回匹配结果; - 然后触发
AutoConfigurationImportEvent
事件,告诉ConditionEvaluationReport
条件评估报告器对象来分别记录符合条件和exclude
的自动配置类。 - 最后spring再将最后筛选后的自动配置类导入IOC容器中