Spring Boot - IOC(二)
【接前章】
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 在这里了
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}上一篇IOC容器已经准备好了,下面到了IOC容器最核心的部分:refresh。
0. refreshContext
它直接调了refresh方法(注意此时还是 SpringApplication,没有进到真正的IOC容器),后面又注册了一个关闭的钩子。这个 registerShutdownHook 方法的文档注释:
Register a shutdown hook with the JVM runtime, closing this context on JVM shutdown unless it has already been closed at that time.
向JVM运行时注册一个shutdown的钩子,除非JVM当时已经关闭,否则在JVM关闭时关闭上下文。
可以大概看出来,这个钩子的作用是监听JVM关闭时销毁IOC容器和里面的Bean。这里面有一个很经典的应用:应用停止时释放数据库连接池里面的连接。
下面咱来看这个refresh方法:
没有什么复杂的逻辑,它会直接强转成 AbstractApplicationContext,调它的refresh方法。之前我们有了解过,AbstractApplicationContext 中的 refresh 是IOC容器启动时的最核心方法:
这个方法非常长,一共有13个步骤,本篇我们来看前3个步骤:
1. prepareRefresh:初始化前的预处理
最前面先记录启动时间,标记IOC容器状态,之后要开始初始化属性配置:
1.1 initPropertySources:初始化属性配置
这个方法是一个模板方法,留给子类重写,默认不做任何事情。
借助IDEA,发现这个方法在 GenericWebApplicationContext 中有重写,而 AnnotationConfigServletWebServerApplicationContext 恰好继承了它。
它最终又调到 Environment 的 initPropertySources 中。StandardServletEnvironment 是唯一重写这个方法的:
继续追踪 WebApplicationContextUtils.initServletPropertySources:
这个方法的文档注释:
Replace Servlet-based stub property sources with actual instances populated with the given servletContext and servletConfig objects. This method is idempotent with respect to the fact it may be called any number of times but will perform replacement of stub property sources with their corresponding actual property sources once and only once.
将基于Servlet的存根属性源替换为使用给定 ServletContext 和 ServletConfig 对象填充的实际实例。
关于此方法可以调用任意次的事实,它是幂等的,但是将用其相应的实际属性源执行一次且仅一次的存根属性源替换。
通过大概的阅读文档注释和内部的两个if,可以大概确定它是把 Servlet 的一些初始化参数放入IOC容器中(类似于 web.xml 中的参数放入IOC容器)。
回到prepareRefresh方法:
1.2 validateRequiredProperties:属性校验
从调用的两步来看,它是要检验一些必需的属性是否为空,如果有null的属性会抛出异常。从源码的英文单行注释中可以看到,它与 ConfigurablePropertyResolver 的 setRequiredProperties 方法有关。翻看这个方法的文档注释:
Specify which properties must be present, to be verified by validateRequiredProperties().
指定必须存在哪些属性,以通过 validateRequiredProperties 方法进行验证。
它是说指定了属性,就可以通过 validateRequiredProperties 方法校验。那到底有没有字段校验呢?咱通过Debug来看一眼:

。。。。。。根本就没有要校验的。。。那这一步就跳过去吧。。。。。。
回到 prepareRefresh 方法:
这个早期事件,目前还不好解释,得联系后面的一个组件来解释。
2. obtainFreshBeanFactory:获取BeanFactory,加载所有bean的定义信息
源码非常简单,先刷新后获取。
2.1 refreshBeanFactory
发现它是一个抽象方法,留给子类重写。对于XML配置的IOC容器,和注解配置的IOC容器,分别有一种实现。借助IDEA,发现 GenericApplicationContext 和 AbstractRefreshableApplicationContext 重写了它。根据前面的分析,AnnotationConfigServletWebServerApplicationContext 继承了 GenericApplicationContext,故咱来看它的 refreshBeanFactory 方法:
逻辑很简单,只是设置了 BeanFactory 的序列化ID而已。
2.1.1 【扩展】基于XML的refreshBeanFactory
上面看到有两个子类重写了这个方法(XML和注解的),基于XML配置的IOC容器,在这一步要做的事情要更复杂,简单扫一眼:
可以发现逻辑更复杂。简单来看一下吧:
如果已经有 BeanFactory 了,销毁Bean和 BeanFactory 。之后创建一个 BeanFactory,设置序列化ID,执行自定义 BeanFactory 的逻辑,之后加载Bean定义,最后设置到IOC容器中。
这其中有xml的加载和读取,由于 SpringBoot 已经几乎放弃xml配置,全部通过注解和 JavaConfig 来配置应用,故不再深入研究。
2.2 getBeanFactory
更简单了,不必多言。
3. prepareBeanFactory:BeanFactory的预处理配置
在源码中发现了一个组件概念:**BeanPostProcessor**。这个概念非常非常重要,我们先来了解一下它。
【如果小伙伴不是很了解或不了解 **BeanPostProcessor**,请继续往下看;对 BeanPostProcessor 很熟悉的小伙伴可以跳过第3.0节】
3.0 【重要】BeanPostProcessor
它的文档注释原文翻译:
Factory hook that allows for custom modification of new bean instances, e.g. checking for marker interfaces or wrapping them with proxies. ApplicationContexts can autodetect BeanPostProcessor beans in their bean definitions and apply them to any beans subsequently created. Plain bean factories allow for programmatic registration of post-processors, applying to all beans created through this factory. Typically, post-processors that populate beans via marker interfaces or the like will implement postProcessBeforeInitialization, while post-processors that wrap beans with proxies will normally implement postProcessAfterInitialization.
这个接口允许自定义修改新的Bean的实例,例如检查它们的接口或者将他们包装成代理对象等,
ApplicationContexts能自动察觉到我们在 BeanPostProcessor 里对对象作出的改变,并在后来创建该对象时应用其对应的改变。普通的bean工厂允许对后置处理器进行程序化注册,它适用于通过该工厂创建的所有bean。
通常,通过标记接口等填充bean的后处理器将实现 postProcessBeforeInitialization,而使用代理包装bean的后处理器将实现 postProcessAfterInitialization。
它通常被称为 “Bean的后置处理器”,它的作用在文档注释中也描述的差不多,它可以在对象实例化但初始化之前,以及初始化之后进行一些后置处理。
可以这样简单理解 Bean 的初始化步骤,以及 BeanPostProcessor 的切入时机:

下面用一个实例快速感受 BeanPostProcessor 的作用。
3.0.1 BeanPostProcessor的使用
声明一个 Cat 类:
再声明一个 CatBeanPostProcessor:
BeanPostProcessor 可以在前后做一些额外的处理。
接下来,编写一个配置类,并创建这个Cat对象:
启动IOC容器,并获取这个Cat,打印它的name,发现打印输出是dog,证明后置处理器已经起作用了。
3.0.2 【执行时机】Bean初始化的顺序及BeanPostProcessor的执行时机
我们在学过 SpringFramework 的时候,知道Bean的几种额外的初始化方法的指定(init-method,@PostConstruct,InitializingBean接口)。那么它们以及构造方法的执行顺序,以及 BeanPostProcessor 的执行时机分别是什么呢?我们修改上面的代码来测试一下:
修改 Cat:
修改 CatBeanPostProcessor:
重新启动IOC容器,打印结果如下:
由此可得结论:
初始化执行顺序:
构造方法
@PostConstruct/init-methodInitializingBean的afterPropertiesSet方法
BeanPostProcessor的执行时机
before:构造方法之后,
@PostConstruct之前after:
afterPropertiesSet之后
出现这种情况的原理,我们可以先翻看文档注释,等到后面初始化单实例Bean时会有源码解析。
@PostConstruct
The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization. This method MUST be invoked before the class is put into service. This annotation MUST be supported on all classes that support dependency injection.
PostConstruct注解,用于标注在需要依赖注入完成,以执行任何初始化之后需要执行的方法上。在Bean投入使用之前必须调用此方法。所有支持依赖注入的类都必须支持该注解。
InitializingBean
Interface to be implemented by beans that need to react once all their properties have been set by a BeanFactory: e.g. to perform custom initialization, or merely to check that all mandatory properties have been set. An alternative to implementing InitializingBean is specifying a custom init method, for example in an XML bean definition.
由 BeanFactory 设置完所有属性后需要作出反应的bean所实现的接口:执行自定义初始化,或仅检查是否已设置所有必填属性。
实现InitializingBean的替代方法是指定自定义 init-method,例如在XML bean定义中。
BeanPostProcessor:
before
Apply this BeanPostProcessor to the given new bean instance before any bean initialization callbacks (like InitializingBean's afterPropertiesSet or a custom init-method). The bean will already be populated with property values. The returned bean instance may be a wrapper around the original.
在任何bean初始化回调(例如 InitializingBean的afterPropertiesSet 或 自定义init-method)之前,将此 BeanPostProcessor 应用于给定的新bean实例。该bean将已经用属性值填充。返回的bean实例可能是原始实例的包装。
after
Apply this BeanPostProcessor to the given new bean instance after any bean initialization callbacks (like InitializingBean's afterPropertiesSet or a custom init-method). The bean will already be populated with property values. The returned bean instance may be a wrapper around the original.
在任何bean初始化回调(例如InitializingBean的afterPropertiesSet 或 自定义init-method)之后,将此 BeanPostProcessor 应用于给定的新bean实例。该bean将已经用属性值填充。返回的bean实例可能是原始实例的包装。
了解 BeanPostProcessor 后,来看下面几个片段:
3.1 addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
它先配置了一个 ApplicationContextAwareProcessor,之后又忽略了下面几个接口。它这么做的原因是什么呢?咱不妨先来看看 ApplicationContextAwareProcessor 是什么。
3.1.1 ApplicationContextAwareProcessor
它的文档注释原文翻译:
BeanPostProcessor implementation that passes the ApplicationContext to beans that implement the EnvironmentAware, EmbeddedValueResolverAware, ResourceLoaderAware, ApplicationEventPublisherAware, MessageSourceAware and/or ApplicationContextAware interfaces. Implemented interfaces are satisfied in order of their mention above. Application contexts will automatically register this with their underlying bean factory. Applications do not use this directly.
BeanPostProcessor 实现,它将 ApplicationContext 传递给实现 EnvironmentAware,EmbeddedValueResolverAware,ResourceLoaderAware,ApplicationEventPublisherAware,MessageSourceAware 和/或 ApplicationContextAware 接口的bean。
按照上面提到的顺序满足已实现的接口。
IOC容器将自动在其基础bean工厂中注册它。应用程序不直接使用它。
看到这段文档注释,就已经能明白上面的几个ignore的意义了。我们再看一眼源码,便更能理解它的设计了:
它果然在挨个判断,然后注入。
3.2 registerResolvableDependency:自动注入的支持
上面的单行注释翻译:
BeanFactory 接口未在普通工厂中注册为可解析类型。
MessageSource 注册为Bean(并发现用于自动装配)。
上面一句还能看懂,下面是干什么?讲真我也不是很清楚,咱还是看看这个方法的文档注释吧。
Register a special dependency type with corresponding autowired value. This is intended for factory/context references that are supposed to be autowirable but are not defined as beans in the factory: e.g. a dependency of type ApplicationContext resolved to the ApplicationContext instance that the bean is living in. Note: There are no such default types registered in a plain BeanFactory, not even for the BeanFactory interface itself.
用相应的自动装配值注册一个特殊的依赖类型。
这适用于应该是可自动执行但未在工厂中定义为bean的工厂/上下文引用:类型为 ApplicationContext 的依赖关系已解析为Bean所在的 ApplicationContext 实例。
注意:在普通 BeanFactory 中没有注册这样的默认类型,甚至 BeanFactory 接口本身也没有。
它大概的意思是如果遇到一个特殊的依赖类型,就使用一个特殊的预先准备好的对象装配进去。
它的方法实现(仅在 DefaultListableBeanFactory 中有实现):
前面的判断都不看,底下有一个put操作,key和value分别是依赖的类型和自动注入的值。
这个 resolvableDependencies 是个Map,它的注释:
从依赖项类型映射到相应的自动装配值。
至此,它的功能已经明确了:它可以支持一些特殊依赖关系的类型,并放到 resolvableDependencies 集合中保存,使得能在任意位置注入上述源码中的组件。
3.3 addBeanPostProcessor(new ApplicationListenerDetector(this))
又注册了一个后置处理器,来看 ApplicationListenerDetector 的文档注释:
BeanPostProcessor that detects beans which implement the ApplicationListener interface. This catches beans that can't reliably be detected by getBeanNamesForType and related operations which only work against top-level beans.
BeanPostProcessor,用于检测实现 ApplicationListener 接口的bean。这将捕获 getBeanNamesForType 和仅对顶级bean有效的相关操作无法可靠检测到的bean。
文档注释还是比较容易理解的,它是来收集 ApplicationListener 的。再来看看它的源码核心部分:
逻辑还是比较简单的,如果Bean是 ApplicationListener 的实现类,并且是单实例Bean,则会注册到IOC容器中。
4. postProcessBeanFactory:BeanFactory的后置处理
在 AbstractApplicationContext 中,这个方法又被设置成模板方法了:
借助IDEA,发现 AnnotationConfigServletWebServerApplicationContext 重写了这个方法。
它首先调了父类 ServletWebServerApplicationContext 的 postProcessBeanFactory 方法。
4.1 ServletWebServerApplicationContext.postProcessBeanFactory
4.1.1 注册WebApplicationContextServletContextAwareProcessor
它的文档注释原文翻译:
Variant of ServletContextAwareProcessor for use with a ConfigurableWebApplicationContext. Can be used when registering the processor can occur before the ServletContext or ServletConfig have been initialized.
ServletContextAwareProcessor 的扩展,用于 ConfigurableWebApplicationContext 。可以在初始化 ServletContext 或 ServletConfig 之前进行处理器注册时使用。
似乎看不出什么很明显的思路,但它说是 ServletContextAwareProcessor 的扩展,那追到 ServletContextAwareProcessor 的文档注释:
BeanPostProcessor implementation that passes the ServletContext to beans that implement the ServletContextAware interface. Web application contexts will automatically register this with their underlying bean factory. Applications do not use this directly.
将 ServletContext 传递给实现 ServletContextAware 接口的Bean的 BeanPostProcessor 实现。
Web应用程序上下文将自动将其注册到其底层bean工厂,应用程序不直接使用它。
发现很明白,它是把 ServletContext、ServletConfig 注入到组件中。它的核心源码:
跟上一篇中的注册几乎是一个套路,不再赘述。
4.1.2 registerWebApplicationScopes
这个方法没有任何注释,只能靠里面的源码来试着推测。
4.1.2.1 ExistingWebApplicationScopes
从字面意思上看,它是表示在Web应用上已经存在的作用域。它的部分源码:
发现它确实是缓存了两种scope,分别是 request 域和 session 域。
下面的 restore 方法,是把现在缓存的所有作用域,注册到 BeanFactory 中。
大概猜测这是将Web的request域和session域注册到IOC容器,让IOC容器知道这两种作用域(学过 SpringFramework 都知道Bean的作用域有request 和 session)。
4.1.2.2 WebApplicationContextUtils.registerWebApplicationScopes
它的文档注释原文翻译:
Register web-specific scopes ("request", "session", "globalSession", "application") with the given BeanFactory, as used by the WebApplicationContext.
使用WebApplicationContext使用的给定BeanFactory注册特定于Web的作用域(“request”,“session”,“globalSession”,“application”)。
注释很清晰,将Web的几种作用域注册到 BeanFactory 中。
源码中注册了 request 、session 、application 域,还注册了几种特定的依赖注入的关系。
回到 AnnotationConfigServletWebServerApplicationContext 中:
下一步是进行组件的包扫描。不过注意一点,在这个位置上打断点,Debug运行时发现 basePackages 为null,故此处不进,小伙伴不要觉得之前已经知道了 primarySource 就觉得这个地方 basePackages 就肯定有值了。
咱先了解下这个包扫描,方便后续咱们看到时理解。
4.2 【重要】包扫描
在 AnnotationConfigServletWebServerApplicationContext 中有声明 注解Bean定义解析器 和 类路径Bean定义扫描器 的类型,可以依此类型来查看原理。
又出现 scan 和 doScan 了。doScan 方法:
4.2.1 findCandidateComponents
来到父类 ClassPathScanningCandidateComponentProvider 的 findCandidateComponents 方法:
很明显,包扫描进入的是下面的 scanCandidateComponents :
4.2.2 scanCandidateComponents
这个方法中有大量log,精简篇幅如下:
首先它将要扫描的包和一些前缀进行拼接:前缀是 classpath*: ,后缀默认扫 **/*.class ,中间部分调了一个 resolveBasePackage 方法,这个方法其实不看也能猜出来是把这个包名转换成文件路径(不然怎么拼接到扫描路径呢)。看一眼源码:
果然是包名转换,把 . 转换成 / 。
由此可算得,入门启动程序中的拼接包路径应该是: classpath*:com/example/demo/**/*.class 。
回到方法中:
下面的 getResources 方法最终会拿 ResourcePatternResolver 来获取一组 Resource ,分开来看:
4.2.3 getResourcePatternResolver
可以发现它用的是 PathMatchingResourcePatternResolver 。那下面的 getResources 方法就是它里面的了:
4.2.4 getResources:包扫描
整个方法的大if结构中,先判断要扫描的包路径是否有 classpath*: ,有则截掉,之后判断路径是否能匹配扫描规则。而这个规则的匹配器,通过IDEA发现 PathMatcher 只有一个实现类: AntPathMatcher ,由此也解释了 SpringFramework 支持的是ant规则声明包。
如果规则匹配,则会进入下面的 findPathMatchingResources 方法:
4.2.4.1 findPathMatchingResources:根据Ant路径进行包扫描
首先它要截取扫描根路径,这个截取是一个简单算法:
通过上面的算法,可以计算得路径是 classpath*:com/example/demo/ 。
回到上面的方法:
在截取完成后,又把根路径传入了那个 getResources 方法。由于这一次没有后缀了,只有根路径,故进入的分支方法会不一样:
这次路径是 classpath*:com/example/demo/ ,不能匹配了,进入下面的else部分,findAllClassPathResources 方法:
4.2.4.2 findAllClassPathResources
这里进行真正的扫描获取包的工作:doFindAllClassPathResources
4.2.4.3 doFindAllClassPathResources
可以发现它在做的工作是使用类加载器,把传入的根包以Resource的形式加载出来,以便后续的文件读取。
之后方法返回,回到4.2.4.1的方法中:
4.2.4.4 扫描包
它要拿到所有要扫描的包的文件路径,来进行真正的包扫描工作。
首先 resolveRootDirResource 方法在实现中直接把 rootDirResource 返回了(不知道这什么鬼才操作),之后要判断扫描包路径前缀是否为vfs或jar,都不是则进入最底下的方法(默认情况下我们只配置扫描本项目的组件,则扫描最终的扫描包路径前缀为 file:/)。
4.2.4.5 doFindPathMatchingFileResources
这个方法默认在 PathMatchingResourcePatternResolver 中有实现,但通过IDEA发现它被 ServletContextResourcePatternResolver 重写了。通过Debug,走到这一步也发现先进的 ServletContextResourcePatternResolver 中的 doFindPathMatchingFileResources 方法。
然而在默认的项目内部包扫描中,与 ServletContextResource 没有关系,故还是要回到 PathMatchingResourcePatternResolver 中。
4.2.4.6 PathMatchingResourcePatternResolver.doFindPathMatchingFileResources
try中先把文件加载出来,之后调了下面的 doFindMatchingFileSystemResources 方法:
源码中很明显只有一句是核心: retrieveMatchingFiles :
4.2.4.7 retrieveMatchingFiles
上面的一些必要的检查之后,它将路径进行整理,最终调用 doRetrieveMatchingFiles 方法:
4.2.4.8 doRetrieveMatchingFiles:递归扫描
这个方法是最终进行包扫描的底层,可以发现在16行使用了递归扫描。
扫描完成后, getResources 方法算是彻底执行完成,回到4.2.2 scanCandidateComponents 方法中:
4.2.5 解析Component
下面要遍历每个扫描出来的.class文件(此时还没有进行过滤),来下面的try部分。
它使用了一个 MetadataReader 来解析.class文件,它就可以读取这个class的类定义信息、注解标注信息。之后要用 MetadataReader 来判断这个class是否为一个 Component:
4.2.5.1 isCandidateComponent
它拿了一组 excludeFilters 和 includeFilters,而这两组过滤器通过Debug可以发现:

它会判断class是否被 @Component / @ManagedBean 标注。至此发现了真正扫描 @Component 的原理。
判定为 Component 后,会将这个class封装为 BeanDefinition,最后返回。
这部分的逻辑非常复杂,咱用一个图来理解这部分过程:

返回到4.2中的doScan中:
4.3 扫描完BeanDefinition后
它要遍历每个 BeanDefinition,进行一些后置处理。
4.3.1 beanNameGenerator.generateBeanName
之前我们见过它,它的作用到后面加载它的时候咱再看。
4.3.2 postProcessBeanDefinition
发现是设置 BeanDefinition 的一些默认值。
4.3.3 checkCandidate
字面意思是检查候选者,不是很好理解,来看这个方法的文档注释:
Check the given candidate's bean name, determining whether the corresponding bean definition needs to be registered or conflicts with an existing definition.
检查给定候选者的Bean名称,以确定是否需要注册相应的Bean定义或与现有定义冲突。
原来它是检查BeanName是否冲突的。
4.3.4 registerBeanDefinition
既然上面的检查不冲突,就可以进入到if结构体中,最后就可以注册 BeanDefinition 。
遇见了熟悉的方法,这个方法之前已经看过了,不再详细解析。(第10篇4.9.4.6章节)
至此,BeanFactory 的后置处理就结束了,但下面还有一组后置处理器,也是对 BeanFactory 进行处理。
5. invokeBeanFactoryPostProcessors:执行BeanFactory创建后的后置处理器
注意这里又出现了一个新的概念:**BeanFactoryPostProcessor**。
5.0 【重要】BeanFactoryPostProcessor
BeanFactoryPostProcessor(BeanFactory后置处理器)是一个接口,它只定义了一个方法:
实现了这个接口,BeanFactory 标准初始化完毕后,可以对这个 BeanFactory 进行后置处理。
这个时机下,所有的 **BeanDefinition** 已经被加载,但没有Bean被实例化。
另外,BeanFactoryPostProcessor 还有一个子接口:**BeanDefinitionRegistryPostProcessor**(Bean定义注册的后置处理器)
它额外定义了一个方法:
它的执行时机是所有Bean的定义信息即将被加载但未实例化时,也就是先于 **BeanFactoryPostProcessor**。
【规律】BeanPostProcessor 是对Bean的后置处理,BeanFactoryPostProcessor 是对 BeanFactory 的后置处理,后续再看到这样的同理。
回到源码:
5.1 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors:回调后置处理器
这段源码非常长(130行+),为了浏览方便,我直接把关键注释写在源码中了。
这一部分非常重要。可以简单地这样理解:

将这段源码分成几部分来看:
5.1.1 参数中的PostProcessor分类
遍历一次后,把同时也是 BeanDefinitionRegistryPostProcessor 的后置处理器单独挑出来,直接回调 postProcessBeanDefinitionRegistry 方法。
通过Debug,发现传入这个方法的参数中,beanFactoryPostProcessors 有3个:

这里面同时属于 BeanDefinitionRegistryPostProcessor 有两个:

5.1.2 BeanFactory中取+排序+回调
它把 BeanFactory 中所有的 BeanDefinitionRegistryPostProcessor 分成三部分:实现 PriorityOrdered 接口的、实现 Ordered 接口的,普通的。
上面的两部分源码就是对前两种方式进行回调:筛选,排序,注册,回调,清除。
之后又用同样的逻辑,取所有的 BeanFactoryPostProcessor ,进行同样的操作,不再重复描述。
逻辑不算复杂,下面介绍几个重要的后置处理器。
5.2 【重要扩展】ConfigurationClassPostProcessor
在上述源码的Debug中,第二环节获取所有 BeanDefinitionRegistryPostProcessor 的时候发现了一个后置处理器:ConfigurationClassPostProcessor 。

它的文档注释原文翻译:
BeanFactoryPostProcessor used for bootstrapping processing of @Configuration classes. Registered by default when using <context:annotation-config/> or <context:component-scan/>. Otherwise, may be declared manually as with any other BeanFactoryPostProcessor. This post processor is priority-ordered as it is important that any Bean methods declared in @Configuration classes have their corresponding bean definitions registered before any other BeanFactoryPostProcessor executes.
BeanFactoryPostProcessor,用于 @Configuration 类的扫描加载处理。 使用<context:annotation-config /> 或 <context:component-scan /> 时默认注册。否则,可以像其他任何 BeanFactoryPostProcessor 一样手动声明。 此后处理器按优先级排序,因为在 @Configuration 标注的类中声明的任何Bean方法在执行任何其他 BeanFactoryPostProcessor 之前都要注册其相应的Bean定义,这一点很重要。
那自然我们应该去看它的 postProcessBeanDefinitionRegistry 方法:
它取出 BeanFactory 的id,并在下面的if结构中判断是否已经被调用过了。确定没有,在被调用过的集合中加上当前 BeanFactory 的id,之后调用 processConfigBeanDefinitions 方法:
源码中有几部分是比较复杂且重要的环节,咱们一一来看:
5.2.1 确定配置类和组件
这里面需要关注的几个 ConfigurationClassUtils 方法:
isFullConfigurationClass:判断一个配置类是否为full类型isLiteConfigurationClass:判断一个配置类是否为lite类型checkConfigurationClassCandidate:检查一个类是否为配置类
上面提到了两个类型,都是可以从源码中看到的。那这个full和lite都是什么呢?
5.2.1.1 full与lite
其实,了解full和lite,只需要到最后一个方法 checkConfigurationClassCandidate 中看一下就知道了:
前面大段的代码都是校验和获取注解标注信息(已省略),核心的源码在底下的if-else结构中。它会调 isFullConfigurationCandidate 和 isLiteConfigurationCandidate 来校验Bean的类型,而这两个方法的声明:
由这段源码可以得知:
full:
@Configuration标注的类lite:有
@Component、@ComponentScan、@Import、@ImportResource标注的类,以及@Configuration中标注@Bean的类。
5.2.2 加载获取BeanNameGenerator
它要在这个地方获取 BeanNameGenerator ,然而通过Debug发现它是null,故先放一边。
5.2.3 解析配置类 与 包扫描的触发时机
这一段第一句就是核心:parse
它要遍历每一个 BeanDefinition,并根据类型来决定如何解析。SpringBoot 通常使用注解配置,这里会进入第一个if结构:
上面的方法又调到下面,下面的方法中先进行判断。这里的 existingClass 容易被误解,它在这个方法的最后,把当前传入的组件存到一个Map中,每次组件进到这个方法时先校验是否有这个类型的Bean了,如果有,要进行一些处理。如果没有,往下走,进入到do-while结构中,它要执行 doProcessConfigurationClass 方法。
从源码注释中已经看出,它来解析 @PropertySource 、@ComponentScan 、@Import 、@ImportResource 、@Bean 等注解,并整理成一个 ConfigClass 。
由此可知,在这一步,一个配置类的所有信息就已经被解析完成了。
咱们以解析 @ComponentScan 为例:
5.2.3.1 解析 @ComponentScan
这部分解析要追踪到 componentScanParser 的parse方法中,它用来真正的做注解解析:
5.2.3.2 ComponentScanAnnotationParser.parse
(这里只记录重要的部分,中间省略的部分小伙伴们可借助IDE查看)
先看一眼最后的return:doScan 方法!原来包扫描的触发时机在这里:执行 **ConfigurationClassPostProcessor** 的 **postProcessBeanDefinitionRegistry** 方法,解析 **@ComponentScan** 时触发。
除了最后的 doScan,这里面有一个关注的点:
5.2.3.3 new ClassPathBeanDefinitionScanner
这个构造方法中有点细节:
终于找到这个 BeanNameGenerator 的类型了:AnnotationBeanNameGenerator 。
5.2.3.4 【扩展】AnnotationBeanNameGenerator 的Bean名称生成规则
从重写的方法开始:
先执行下面的 determineBeanNameFromAnnotation 方法,看这些模式注解上是否有显式的声明 value 属性,如果没有,则进入下面的 buildDefaultBeanName 方法,它会取类名的全称,之后调 Introspector.decapitalize 方法将首字母转为小写。
5.2.4 loadBeanDefinitions:解析配置类中的内容
这里它会循环所有的配置类,去加载配置类里面的Bean定义信息。继续往下看:
由于在之前已经解析过这个 configClass 了,所以在这里可以很容易的解析出这里面的 @Import 、标注了 @Bean 的方法、@ImportResource 等,并进行相应处理。
咱们以 读取 @Bean 注解标注的方法为例,看一眼它对Bean的解析和加载:(方法很长,关键注释已标注在源码中)
5.2.5 加载配置类中的未加载完成的被@Bean标注的组件
这部分的判断比较有趣:在上面的配置类都加载完成后,它要比对 BeanDefinition 的个数,以及被处理过的数量。只要数量不对应,就会展开那些配置类继续加载。这部分的源码与上面比较类似,只是检测逻辑的不同,小册不再详细展开,有兴趣的小伙伴可以自行Debug看一下效果。
最后更新于
这有帮助吗?