Spring Boot 源码解析 (版本2.3.x)

阅读指南: 通篇文章使用先总后分的形式,读者可以根据里面的链接跳转到对应章节阅读具体内容。

启动原理分析

Main方法启动

SpringApplicationMain.png

SpringApplication构造方法

SpringApplication.png

  • primarySources : 加载的主要资源类
  • webApplicationType : Web应用类型(REACTIVE/NONE/SERVLET),默认Servlet
  • 根据 Spring SPI机制 实例化ApplicationContextInitializer、ApplicationListener
  • mainApplicationClass : 获取当前main方法

deduceMainApplicationClass方法根据新建RuntimeException,获取当前堆栈,遍历该堆栈查找main方法,很新颖的写法

deduceMainApplicationClass.png

run方法解析

SpringApplicationRun.png

  • StopWatch用于记录启动信息,包括启动耗时,用于日志输出
  • configureHeadlessProperty 设置Headless模式
  • prepareEnvironment 准备环境
    • getOrCreateEnvironment 默认类型StandardServletEnvironment,获取jvm环境变量、系统环境变量,Servlet参数等
    • configureEnvironment 将Profiles、Arguments(启动参数)、ConversionService(类型转换,初始136个)设置到Spring运行环境
    • ConfigurationPropertySources.attach ConfigurationPropertySources
    • listeners.environmentPrepared 将 application.properties 加载到Environment中
    • bindToSpringApplication 将属性绑定 SpringApplication ,如spring.main.allow-bean-definition-overriding绑定到allowBeanDefinitionOverriding变量
  • configureIgnoreBeanInfo 设置是否跳过对BeanInfo类的搜索 , 详见CachedIntrospectionResults
  • printBanner 打印启动图标(公司logo)
  • createApplicationContext 根据前面的webApplicationType创建上下文,默认AnnotationConfigServletWebServerApplicationContext
  • 初始化异常报告输出SpringBootExceptionReporter
  • prepareContext 准备上下文(核心方法)
  • refreshContext 刷新上下文(核心方法)
  • afterRefresh 默认空实现
  • listeners.started 触发started事件,默认触发EventPublishingRunListener
  • callRunners 扩展点之一,用于工程启动完成之后执行的

SpringFactoriesLoader

ps: 对于不清楚什么是SPI机制的可自行百度 “JAVA SPI”

Spring SPI配置文件位于类路径下的META-INF/spring.factories

spring-factories.png

相比于jdk自带的ServiceLoader,或者Dubbo的ExtensionLoader,整个SpringFactoriesLoader对于SPI的实现就显得十分轻量、清晰,主要也就一个loadSpringFactories方法,主要逻辑也就是读取spring.factories里面的配置,然后通过类路径实例化类

loadSpringFactories.png

ConfigurationPropertySources

TODO

ConfigFileApplicationListener

实现配置文件加载,Profile属性加载等。

loadPostProcessors.png

默认的EnvironmentPostProcessor包括: 执行顺序为 ↓

  • SystemEnvironmentPropertySourceEnvironmentPostProcessor 封装systemEnvironment为OriginAwareSystemEnvironmentPropertySource,用于程序异常报错时,定位报错属性。详细可参考TextResourceOrigin、PropertySourceOrigin、OriginTrackedPropertiesLoader等。
  • SpringApplicationJsonEnvironmentPostProcessor jvm启动参数设置spring.application.json传json格式配置,或者环境变量添加SPRING_APPLICATION_JSON来设置spring boot配置
  • CloudFoundryVcapEnvironmentPostProcessor 运行环境为 Cloud Foundry(开源PaaS私有云平台) 时相关配置
  • ConfigFileApplicationListener(自身)
  • DebugAgentEnvironmentPostProcessor 响应式模式调试代理层,默认如果找到reactor.tools.agent.ReactorDebugAgent则开启,可以通过设置spring.reactor.debug-agent.enabled为false进行关闭

通过addPropertySources方法创建Loader实例并调用load方法(核心方法)

load.png

  • FilteredPropertySource.apply //TODO 暂时还没完全理解用处。。。
  • initializeProfiles 初始化Profile
  • load 根据 规则 查找加载配置文件, 规则如下:
    • classpath:/ 类路径
    • classpath:/config/ 类路径下的config文件夹
    • file:./ jar包所在目录
    • file:./config/*/ jar包所在路径下的config文件夹
    • file:./config/ jar包所在路径下的config文件夹

配置相关属性:

  • spring.config.additional-location 可增加额外配置路径,比如除了想加载默认application.properties,还想加载自定义的properties则可以通过设置该参数进行扩展
  • spring.config.location 若上面的读取规则无法满足需要,则可以自行定义读取规则,比如外置配置文件实现配置分离
  • spring.config.name 自定义配置文件名,比如读取module.properties则配置为module,若想配置多个则用”,”分隔

关于jdk1.8 lambda写法

  1. load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
  2. load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
  3. private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
  4. // do something...
  5. }
  6. private DocumentFilter getPositiveProfileFilter(Profile profile) {
  7. // do something
  8. }
  9. private DocumentFilter getNegativeProfileFilter(Profile profile) {
  10. // do something
  11. }

一开始对于这种写法还是比较迷惑,load的第二个参数明明是需要传入DocumentFilterFactory类型参数,而getPositiveProfileFilter及getNegativeProfileFilter都是返回的DocumentFilter,怎么传得进去?

解答:

  1. @FunctionalInterface
  2. private interface DocumentFilterFactory {
  3. DocumentFilter getDocumentFilter(Profile profile);
  4. }

DocumentFilterFactory只有一个getDocumentFilter接口,而这个接口的构造跟而getPositiveProfileFilter及getNegativeProfileFilter是一样的,所以这里的this::getPositiveProfileFilter,this::getNegativeProfileFilter实际就是将这两个方法当成一个匿名DocumentFilterFactory实现类传进去,
有点像javascript中的回调函数。

  1. //定义回调方法
  2. var callback = function() {
  3. //do something
  4. }
  5. //调用回调方法
  6. var doCall = function(callback) {
  7. callback = callback || function(){};
  8. callback();
  9. }
  10. //将回调方法当成参数传进去
  11. doCall(callback);

关于BiConsumer用法

问题描述: addToLoaded(MutablePropertySources::addLast, false)方法体中使用addMethod.accept(merged, document.getPropertySource());调用MutablePropertySources::addLast方法,但是accept需要传参两个,而addLast只需要传一个参数,这是为啥???

解答: 看如下例子,如注释中说的 “虽然addLast方法只接受一个参数,而BiConsumer.accept接受两个参数,第一个参数为MyConsumer实例,第二个参数为实际入参,即等价为myConsumer.addLast(54)”

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. import java.util.function.BiConsumer;
  4. public class MyConsumer {
  5. private List<Integer> list = new ArrayList<>();
  6. public MyConsumer() {}
  7. public MyConsumer(List<Integer> list) {
  8. this.list = list;
  9. }
  10. public void addLast(int i) {
  11. list.add(i);
  12. }
  13. public List<Integer> getList() {
  14. return list;
  15. }
  16. public void setList(List<Integer> list) {
  17. this.list = list;
  18. }
  19. public static void main(String[] args) {
  20. BiConsumer<MyConsumer, Integer> biConsumer = MyConsumer::addLast;
  21. MyConsumer myConsumer = new MyConsumer();
  22. myConsumer.addLast(1);
  23. System.out.println(myConsumer.getList()); //输出[1]
  24. biConsumer.accept(myConsumer, 54); //虽然addLast方法只接受一个参数,而BiConsumer.accept接受两个参数,第一个参数为MyConsumer实例,第二个参数为实际入参,即等价为myConsumer.addLast(54)
  25. System.out.println(myConsumer.getList()); //输出[1, 54]
  26. }
  27. }

Binder

Spring Boot 2.x新的属性绑定方式。

  1. DemoProperties properties = Binder.get(environment).bind("demo", Bindable.of(DemoProperties.class)).get();

application.properties配置

  1. demo.count=10000
  2. demo.name=test

Bindable使用详解

  1. Bindable.ofInstance(T instance) //将属性配置设置到当前实例(instance)中
  2. Bindable.of(Class<T> type) //将属性配置到type类型实例中,通过get方法获取该实例
  3. Bindable.listOf(Class<E> elementType) //将属性配置设置到list中,通过get方法获取该list
  4. Bindable.setOf(Class<E> elementType) //将属性配置设置到set中,通过get方法获取该set
  5. Bindable.mapOf(Class<K> keyType, Class<V> valueType) //将属性配置设置到map中,通过get方法获取该map
  6. Bindable.of(ResolvableType type)

属性绑定的大体逻辑: 先根据属性名去environment中查找对应的属性,若找不到则通过遍历迭代的方式,去查找所有的属性并拼接出合适的属性名到environment中进行查找,若找到则设置到对应变量中。详细可查看Binder.bindDataObject方法。

SpringBootExceptionReporter

org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer
org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer
org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer

prepareContext

prepareContext.png

  • context.setEnvironment 设置上下文环境
  • postProcessApplicationContext
  • applyInitializers 调用 ApplicationContextInitializer
  • listeners.contextPrepared 触发 ApplicationContextInitializedEvent
  • 将applicationArguments注册为spring Bean实例,应用可以通过实例名springApplicationArguments获取
  • 将printedBanner注册为Spring Bean实例, 应用可以通过实例名springBootBanner获取
  • 设置allowBeanDefinitionOverriding,若设置为true则允许同名实例进行覆盖,由spring.main.allow-bean-definition-overriding进行设置
  • lazyInitialization 是否延迟初始化
  • load 通过 BeanDefinitionLoader 将从外部传进来的primarySources注册为Spring Bean
  • listeners.contextLoaded 触发 ApplicationPreparedEvent

refreshContext

refreshContext 与传统通过xml配置spring一样都是通过调用AbstractApplicationContext中的refresh方法启动ApplicationContext,所以refreshContext实际就是将启动托付给 Spring 进行。

AbstractApplicationContextRefresh.png
这里重点看onRefresh方法,由 AnnotationConfigServletWebServerApplicationContext 重写,通过createWebServer实现内嵌web容器创建,这也是为什么spring boot可以不用部署在外边容器,而可以直接以jar包形式运行。

详情点击 AnnotationConfigServletWebServerApplicationContext

ApplicationContextInitializer

Spring Boot自带的实现如下:

  • org.springframework.boot.context.config.DelegatingApplicationContextInitializer
  • org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer
  • org.springframework.boot.context.ContextIdApplicationContextInitializer
  • org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer
  • org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer
  • org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
  • org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

EventPublishingRunListener

初始化时,获取从SpringApplication中传过来的ApplicationListener,生命周期各个阶段通过广播方式进行事件触发

Spring Boot自带ApplicationListener如下:

  • org.springframework.boot.ClearCachesApplicationListener
  • org.springframework.boot.builder.ParentContextCloserApplicationListener
  • org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor
  • org.springframework.boot.context.FileEncodingApplicationListener
  • org.springframework.boot.context.config.AnsiOutputApplicationListener
  • org.springframework.boot.context.config.ConfigFileApplicationListener
  • org.springframework.boot.context.config.DelegatingApplicationListener
  • org.springframework.boot.context.logging.ClasspathLoggingApplicationListener
  • org.springframework.boot.context.logging.LoggingApplicationListener
  • org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
  • org.springframework.boot.autoconfigure.BackgroundPreinitializer

EventPublishingRunListener.png

Spring整个生命周期节点与事件关系如下:

  • starting : ApplicationStartingEvent
  • environmentPrepared : ApplicationEnvironmentPreparedEvent
  • contextPrepared : ApplicationContextInitializedEvent
  • contextLoaded : ApplicationPreparedEvent
  • started : ApplicationStartedEvent
  • running : ApplicationReadyEvent
  • failed : ApplicationFailedEvent

Spring整个生命周期触发时间节点关系如下:

  • starting : SpringApplication.run方法体中的listeners.starting触发,此时Spring刚启动
  • environmentPrepared : SpringApplication.prepareEnvironment方法体中的listeners.environmentPrepared触发,此时环境准备完成
  • contextPrepared : SpringApplication.prepareContext方法体中的listeners.contextPrepared触发,此时上下文准备工作完成
  • contextLoaded : SpringApplication.prepareContext方法体中的listeners.contextLoaded触发,此时上下文加载工作完成
  • started : SpringApplication.run方法体中的listeners.started触发,此时Spring上下文已经刷新完成
  • running : SpringApplication.run方法体中的listeners.running触发,此时整个应用启动完成
  • failed : SpringApplication.handleRunFailure方法体中的listeners.failed触发,此时启动失败

Spring整个生命周期触发ApplicationListener关系如下:

方法 事件 ApplicationListener
starting ApplicationStartingEvent LoggingApplicationListener、LiquibaseServiceLocatorApplicationListener、BackgroundPreinitializer
environmentPrepared ApplicationEnvironmentPreparedEvent FileEncodingApplicationListener、AnsiOutputApplicationListener、ConfigFileApplicationListener
、DelegatingApplicationListener、ClasspathLoggingApplicationListener、LoggingApplicationListener
contextPrepared ApplicationContextInitializedEvent -
contextLoaded ApplicationPreparedEvent CloudFoundryVcapEnvironmentPostProcessor、ConfigFileApplicationListener、LoggingApplicationListener
started ApplicationStartedEvent -
running ApplicationReadyEvent BackgroundPreinitializer
failed ApplicationFailedEvent ClasspathLoggingApplicationListener、LoggingApplicationListener、BackgroundPreinitializer

AnnotationConfigServletWebServerApplicationContext

如下为AnnotationConfigServletWebServerApplicationContext的继承关系图,红框里面为核心实现类

AnnotationConfigServletWebServerApplicationContext.png

AnnotationConfigServletWebServerApplicationContext两个成员变量:

实际上这两个成员变量是在手动启动这个ApplicationContext的时候才会生效,默认spring boot启动的时候并不会去执行,而真正启动是在prepareContext启动的时候通过BeanDefinitionLoader进行load的时候才会去启动reader

ServletWebServerApplicationContext为AnnotationConfigServletWebServerApplicationContext的父类,该上下文重写了onRefresh方法,从而实现内嵌web容器创建

ServletWebServerApplicationContextOnRefresh.png
createWebServer.png

  • getServletContext 获取ServletContext,首次为空
  • getWebServerFactory 获取 ServletWebServerFactory,默认获取到TomcatServletWebServerFactory
  • getWebServer 通过ServletWebServerFactory获取Web容器,默认为TomcatWebServer,此时Web容器已经启动完成
  • getSelfInitializer 如下图,getSelfInitializer与selfInitialize的写法为lambda表达式,简化了匿名内部类实现,selfInitialize即为ServletContextInitializer方法中的onStartup实现,被调用的时机是在TomcatWebServer创建的时候被调用

getSelfInitializer.png

  • prepareWebApplicationContext 将当前上下文设置到ServletContext中
  • registerApplicationScope 将ServletContext注册为”application”全局作用域ServletContextScope,并且反向设置为ServletContext的attribute中,以便ContextCleanupListener管理销毁
  • WebApplicationContextUtils.registerEnvironmentBeans 将Servlet运行环境注册到Spring中以便程序使用,比如通过实例名”contextParameters”,”contextAttributes”获取对应参数实例
  • beans.onStartup 调用ServletContextInitializer的onStartup方法,此处扫描ServletRegistrationBean、FilterRegistrationBean、DelegatingFilterProxyRegistrationBean、ServletListenerRegistrationBean及其他自定义ServletContextInitializer,详情可查看ServletContextInitializerBeans

    BeanDefinitionLoader

    顾名思义就是BeanDefinition加载器,支持多种方式的加载行为:

  • Class方式 :通过扫描注解的方式,判断该类是否有@Component注解,如果有则由AnnotatedBeanDefinitionReader执行注册

  • Resource 方式:通过import xml配置文件的方式,由XmlBeanDefinitionReader执行
  • Package 方式:通过扫描指定路径下的注解的方式, 由ClassPathBeanDefinitionScanner执行
  • String方式:通过字符串找到对应配置文件或者Package的方式,执行逻辑由上面2、3进行

image.png

AnnotatedBeanDefinitionReader

主要用于注册Spring Bean,核心逻辑位于doRegisterBean比较简单不细讲,这里有个地方需要注意如下:
image.png
执行构造函数的时候通过AnnotationConfigUtils.registerAnnotationConfigProcessors将支持注解配置的前置器进行注册。详细点击查看

ClassPathBeanDefinitionScanner

扫描指定Package下的所有Spring Bean(即带有@Component|@Repository|@Service|@Controller等注解的类)并进行实例化。整个类本身逻辑不太复杂,核心逻辑位于doScan方法中,主要就是实现扫描所有带Spring Bean注解的类,然后进行一系列判断装饰,筛选过滤,最后将对应的BeanDefinition注册到上下文中。
image.png

ServletWebServerFactory

ServletWebServerFactory通过ServletWebServerFactoryConfiguration进行配置

ServletWebServerFactoryConfiguration.png

Spring Boot官方提供三种内嵌Web容器: Tomcat、Jetty、Undertow,且默认为Tomcat,可通过maven配置启用其他容器,比如启用Jetty:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. <exclusions>
  5. <exclusion>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-tomcat</artifactId>
  8. </exclusion>
  9. </exclusions>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-jetty</artifactId>
  14. </dependency>

EnableAutoConfiguration 运行原理

EnableAutoConfiguration.png
通过查看上图EnableAutoConfiguration注解可知道以下几点:

  • AutoConfigurationPackage : 根据下图中的解释,可以知道如果没有设置basePackages或者basePackageClasses则默认导入AutoConfigurationPackages自身所在的配置类,这就是为啥spring boot启动的时候默认会扫描启动类路径及子路径下的所有Bean等,这里的Registrar继承着ImportBeanDefinitionRegistrar用于直接注册BeanDefinition到上下文中,具体查看ConfigurationClassParser的processImports方法

image.png
image.png

  • AutoConfigurationImportSelector : 继承DeferredImportSelector延迟加载EnableAutoConfiguration配置类,来实现EnableAutoConfiguration的扩展,总体逻辑可能比较绕但实际并不太复杂,读者可以自行调试下主要也就是通过getCandidateConfigurations调用SpringFactoriesLoader来获取配置

image.png
image.png

Configuration注解原理(核心)

  • AnnotatedBeanDefinitionReader 初始化时调用AnnotationConfigUtils.registerAnnotationConfigProcessors方法将ConfigurationClassPostProcessor注册到BeanFactory中
  • ApplicationContext刷新时通过调用PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors,调用ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法
  • processConfigBeanDefinitions 主要处理逻辑如下:
  • postProcessBeanFactory->enhanceConfigurationClasses 中实现Configuration类增强,具体逻辑查看 ConfigurationClassEnhancer

processConfigBeanDefinitions.png
processConfigBeanDefinitions2.png

接下来重点分析以上几个核心步骤内部逻辑:

ConfigurationClassUtils.checkConfigurationClassCandidate

主要逻辑就是通过获取 注解元数据 ,并且判断是否有@Configuration注解

关于AnnotationMetadata.introspect写法解析:AnnotationMetadata是一个接口,然而我们却可以直接调用该接口的方法而不需要new实现类,该方法实现直接写在接口上而且标注为static, 该特性为jdk1.8新增加的,与之一起的还有default实现,详细如下:

  1. default boolean hasMetaAnnotation(String metaAnnotationName) {
  2. return getAnnotations().get(metaAnnotationName,
  3. MergedAnnotation::isMetaPresent).isPresent();
  4. }
  5. static AnnotationMetadata introspect(Class<?> type) {
  6. return StandardAnnotationMetadata.from(type);
  7. }

spring5.x要求jdk1.8以上,其中大量使用1.8以后新增的特性,单纯这些用法也是值得我们学习的

ConfigurationClassParser

解析方法(parse)主要通过调用processConfigurationClass进行解析工作,processConfigurationClass通过将ConfigurationClass转化为SourceClass,然后调用doProcessConfigurationClass(核心方法)
进行解析

SourceClass类Java Doc解释为: Simple wrapper that allows annotated source classes to be dealt with in a uniform manner, regardless of how they are loaded. 意思大概就是该类为简单的Wrapper类(Wrapper可以简单理解为包装类),允许以同一种方式操作处理这些注解类,而无视这些注解类是如何加载的(包括使用Class,以及AnnotationMetadata(注解元数据)).

接下来为doProcessConfigurationClass这个核心方法逻辑解析:

总体逻辑比较复杂比较绕,因为需要支持包括内嵌Configuration的各种用法所有采用递归的方式,先实例化内层Configuration再实例化外层的配置类,逻辑如下:

  • 实例化内嵌的Configuration类
  • @PropertySources及@PropertySource注解支持
  • @ComponentScans及@ComponentScan注解支持
  • @ImportResource及@Import注解支持
  • 方法@Bean注解支持
  • jdk1.8接口方法上@Bean注解支持,如下:
  1. public interface DemoInterface {
  2. @Bean
  3. default String hello1() {
  4. return "hello1";
  5. };
  6. }
  7. @Configuration
  8. public class DemoA implements DemoInterface {
  9. }
  • 还有最重要一点是嵌套子类中也同样支持以上所有特性,doProcessConfigurationClass这个方法里面就是通过递归的方式来遍历所有层次类结构,然后对符合条件的Bean进行注册等。

doProcessConfigurationClass.png


  • processMemberClasses:递归遍历所有内嵌子类,找出所有带@Component、@ComponentScan、@Import、@ImportResource以及方法中带有@Bean注解的类,
    通过递归的方式(调用processConfigurationClass)解析子类
  • processPropertySource解析@PropertySources及@PropertySource注解,详细可查看 PropertySource
  • 通过ComponentScanAnnotationParser进行Bean解析,若为ConfigurationClass则递归调用processConfigurationClass
  • processImports解析@Import注解支持,通过@Import注解导入分为3种情况:
    • ImportSelector :可根据条件自定义导入一个或多个Configuration配置类(详细可查看@EnableAutoConfiguration,@EnableTransactionManagement)
    • ImportBeanDefinitionRegistrar:直接导入Configuration类型的BeanDefinition
    • 普通Configuration类
  • retrieveBeanMethodMetadata扫描成员方法中有@Bean注解的方法
  • processInterfaces扫描接口@Bean方法注解

    ConfigurationClassBeanDefinitionReader

    image.png

    解析逻辑如下:

  • 判断自身是否通过其他Configuration通过注解@Import导入的,或者是其他Configuration类的内嵌配置类,若是则将自己注册到上下文中

  • 扫描所有带@Bean的方法并进行实例化
  • 扫描所有@ImportResource注解对应的xml配置文件,并进行相应实例化
  • 扫描所有@Import注解中的ImportBeanDefinitionRegistrar接口实现

    ConfigurationClassEnhancer

    每个Configuration对象都会通过cglib进行增强,从而与其他Spring Bean一样支持AOP等功能,而ConfigurationClassEnhancer真是增强工具类,内部通过调用Enhancer来实现增强(关于Enhancer后续单独拿出来解析),接下来我们着重分析ConfigurationClassEnhancer这里面的核心逻辑。
    image.png
    这里有三个增强回调方法,分别作用如下:

  • BeanMethodInterceptor :核心拦截器,作用于@Bean注解的方法

  • BeanFactoryAwareMethodInterceptor : 主要是为了兼容BeanFactoryAware中的setBeanFactory,若检查到有这个方法直接跳过拦截调用原始的setBeanFactory方法
  • 其他情况则由NoOp.INSTANCE默认响应

    为啥需要后面这两个拦截器?

以下是一个简单的跨域配置,只有一个corsFilter方法,而实际上真正扫描出来的方法是有6个的,如下图:
image.pngimage.png
总共多了equals、clone、setBeanFactory、toString、hashCode,除了setBeanFactory其他几个比较好理解的,这几个方法属于Object默认自带的方法只要是Java的对象就会有这几个方法,而setBeanFactory又是哪来的呢,那是因为所有的Configuration类增强后都会继承一个EnhancedConfiguration接口,该接口继承自BeanFactoryAware,对此所有的Configuration类都必须实现一个setBeanFactory方法
image.png
image.png

这里就引申了另一个问题为啥需要继承这个BeanFactoryAware接口?

这里有几个关键类:

  • BeanFactoryAwareGeneratorStrategy
  • BeanFactoryAwareMethodInterceptor
  • BeanMethodInterceptor

这几个类通过Spring 的 Enhancer工具类串联在一起来实现@Bean方法的拦截,而BeanFactory这个成员变量就是其中的关键,通过BeanFactory去获取实际的一些Bean。接下来分析这几个类的作用
image.png
BeanFactoryAwareGeneratorStrategy实现为增强类添加一个成员变量$$beanFactory,我们看下实际的增强效果
image.png

备注:可以通过设置System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, “D:\cglib”);来开启cglib文件输出便于我们了解内部结构。

image.png
还有我们稍微解释下上面这个增强类的几个成员变量的意义:

  • $$beanFactory : 这个上面已经有提到,主要就是存放当前上下文
  • CGLIB$CALLBACK_0 :主要存放的是BeanMethodInterceptor这个拦截器
  • CGLIB$CALLBACK_1 :主要存放的是BeanFactoryAwareMethodInterceptor这个拦截器

接着上面继续讲,由BeanFactoryAwareGeneratorStrategy声明的beanFactory成员变量将会被BeanFactoryAwareMethodInterceptor进行赋值,如下:
增强类CorsConfig
EnhancerBySpringCGLIB494xxx实现了setBeanFactory,当Spring启动时调用setBeanFactory进行赋值时,实际上会代理调用BeanFactoryAwareMethodInterceptor这个类,然后将当前上下文赋值给beanFactory。
image.png
image.png
所以到这里$$beanFactory实际已经赋值完成,接下来就是如何使用这个成员变量,而该逻辑主要就是在BeanMethodInterceptor中。BeanMethodInterceptor我们单独哪里分析,查看BeanMethodInterceptor
image.png

BeanMethodInterceptor

image.png
这里的逻辑主要分成以下几种情况:

  • 判断是否为FactoryBean,如果是则通过enhanceFactoryBean调用对应getObject方法获取目标bean
  • 这里有个判断逻辑,我按照注释尝试翻译以下:isCurrentlyInvokedFactoryMethod方法判断当前@Bean对应方法名是否已经被执行实例化话过,比较逻辑是通过方法名与参数类型进行比较,这里不比较返回值是因为为了兼容covariant return type(协变返回值类型,黑人问号?????),我们这里还是解释下什么是covariant return type:重写基类中的方法,且返回值是原方法返回值类型的继承类型,具体例子可以百度下。若第一次执行则通过cglib动态代理调用对应方法进行实例化

image.png

  • 最后这个就是补偿逻辑,如果该方法已经实例化了就获取该实例返回即可。

Conditional条件过滤解析

Condition默认支持类型

  • ConditionalOnBean : 判断是否已经存在Bean实例
  • ConditionalOnClass :判断是否已经存在类
  • ConditionalOnCloudPlatform : 判断是否已经存在指定云平台(目前没用过)
  • ConditionalOnExpression :判断spel表达式是否为true
  • ConditionalOnJava : 判断是否为指定Java版本(兼容性注解)
  • ConditionalOnJndi :判断是否存在指定jndi地址
  • ConditionalOnMissingBean : 判断是否指定Bean还未实例化(参考ConditionalOnBean)
  • ConditionalOnMissingClass :判断是否指定Class未找到(参考ConditionalOnClass)
  • ConditionalOnNotWebApplication :判断当前是否为非web环境
  • ConditionalOnProperty : 判断是否存在某个配置属性
  • ConditionalOnResource :判断是否存在某个配置文件
  • ConditionalOnSingleCandidate : 判断指定Bean在容器中是否只有一个,或者虽然有多个但是指定首选Bean
  • ConditionalOnWarDeployment :判断当前应用打包方式是否为war包
  • ConditionalOnWebApplication:判断当前是否为web环境(参考ConditionalOnNotWebApplication )

    Condition过滤逻辑

    查看源码我们会发现上面每个注解都会有@Conditional(xxxCondition.class)这个注解用于设置一个或多个Condition实现类,该类主要实现了过滤逻辑
    image.png
    image.png ```java public interface Condition {

    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

} ```

主要过滤触发时机

这里我们先介绍ConditionEvaluator这个类,这个类提供一个shouldSkip方法来根据传进去的注解来判断是否需要跳过,而ConditionEvaluator内部实际就是调用Condition进行过滤。

触发时机我们这里主要列举以下几种:

  1. 解析Configuration类之前发生,通过调试我们可以知道过滤触发时机是在ConfigurationClassParser在执行processConfigurationClass方法解析Configuration之前发生,如下图:

image.png

  1. 解析@Bean方法之前发生,通过ConfigurationClassBeanDefinitionReader的loadBeanDefinitionsForBeanMethod方法加载BeanDefinition之前

image.png

  1. 扫描@Component时发生,ClassPathScanningCandidateComponentProvider的scanCandidateComponents方法在扫描到@Component时,通过查看该类上是否带有@ConditionOnXXXX注解,若有则判断是否符合条件,若无则默认不跳过

image.png

AnnotationMetadata

TODO

PropertySource

ComponentScan