- Spring Boot 源码解析 (版本2.3.x)
- 启动原理分析
- SpringApplication构造方法
- run方法解析
- SpringFactoriesLoader
- ConfigurationPropertySources
- ConfigFileApplicationListener
- Binder
- SpringBootExceptionReporter
- prepareContext
- refreshContext
- ApplicationContextInitializer
- EventPublishingRunListener
- AnnotationConfigServletWebServerApplicationContext
- BeanDefinitionLoader
- AnnotatedBeanDefinitionReader
- ClassPathBeanDefinitionScanner
- ServletWebServerFactory
- EnableAutoConfiguration 运行原理
- Configuration注解原理(核心)
- Conditional条件过滤解析
- AnnotationMetadata
- PropertySource
- ComponentScan
- 启动原理分析
Spring Boot 源码解析 (版本2.3.x)
阅读指南: 通篇文章使用先总后分的形式,读者可以根据里面的链接跳转到对应章节阅读具体内容。
启动原理分析
Main方法启动
SpringApplication构造方法
- primarySources : 加载的主要资源类
- webApplicationType : Web应用类型(REACTIVE/NONE/SERVLET),默认Servlet
- 根据 Spring SPI机制 实例化ApplicationContextInitializer、ApplicationListener
- mainApplicationClass : 获取当前main方法
deduceMainApplicationClass方法根据新建RuntimeException,获取当前堆栈,遍历该堆栈查找main方法,很新颖的写法
run方法解析
- 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
相比于jdk自带的ServiceLoader,或者Dubbo的ExtensionLoader,整个SpringFactoriesLoader对于SPI的实现就显得十分轻量、清晰,主要也就一个loadSpringFactories方法,主要逻辑也就是读取spring.factories里面的配置,然后通过类路径实例化类
ConfigurationPropertySources
TODO
ConfigFileApplicationListener
实现配置文件加载,Profile属性加载等。
默认的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方法(核心方法)
- 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写法
load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
// do something...
}
private DocumentFilter getPositiveProfileFilter(Profile profile) {
// do something
}
private DocumentFilter getNegativeProfileFilter(Profile profile) {
// do something
}
一开始对于这种写法还是比较迷惑,load的第二个参数明明是需要传入DocumentFilterFactory类型参数,而getPositiveProfileFilter及getNegativeProfileFilter都是返回的DocumentFilter,怎么传得进去?
解答:
@FunctionalInterface
private interface DocumentFilterFactory {
DocumentFilter getDocumentFilter(Profile profile);
}
DocumentFilterFactory只有一个getDocumentFilter接口,而这个接口的构造跟而getPositiveProfileFilter及getNegativeProfileFilter是一样的,所以这里的this::getPositiveProfileFilter,this::getNegativeProfileFilter实际就是将这两个方法当成一个匿名DocumentFilterFactory实现类传进去,
有点像javascript中的回调函数。
//定义回调方法
var callback = function() {
//do something
}
//调用回调方法
var doCall = function(callback) {
callback = callback || function(){};
callback();
}
//将回调方法当成参数传进去
doCall(callback);
关于BiConsumer用法
问题描述: addToLoaded(MutablePropertySources::addLast, false)方法体中使用addMethod.accept(merged, document.getPropertySource());调用MutablePropertySources::addLast方法,但是accept需要传参两个,而addLast只需要传一个参数,这是为啥???
解答: 看如下例子,如注释中说的 “虽然addLast方法只接受一个参数,而BiConsumer.accept接受两个参数,第一个参数为MyConsumer实例,第二个参数为实际入参,即等价为myConsumer.addLast(54)”
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
public class MyConsumer {
private List<Integer> list = new ArrayList<>();
public MyConsumer() {}
public MyConsumer(List<Integer> list) {
this.list = list;
}
public void addLast(int i) {
list.add(i);
}
public List<Integer> getList() {
return list;
}
public void setList(List<Integer> list) {
this.list = list;
}
public static void main(String[] args) {
BiConsumer<MyConsumer, Integer> biConsumer = MyConsumer::addLast;
MyConsumer myConsumer = new MyConsumer();
myConsumer.addLast(1);
System.out.println(myConsumer.getList()); //输出[1]
biConsumer.accept(myConsumer, 54); //虽然addLast方法只接受一个参数,而BiConsumer.accept接受两个参数,第一个参数为MyConsumer实例,第二个参数为实际入参,即等价为myConsumer.addLast(54)
System.out.println(myConsumer.getList()); //输出[1, 54]
}
}
Binder
Spring Boot 2.x新的属性绑定方式。
DemoProperties properties = Binder.get(environment).bind("demo", Bindable.of(DemoProperties.class)).get();
application.properties配置
demo.count=10000
demo.name=test
Bindable使用详解
Bindable.ofInstance(T instance) //将属性配置设置到当前实例(instance)中
Bindable.of(Class<T> type) //将属性配置到type类型实例中,通过get方法获取该实例
Bindable.listOf(Class<E> elementType) //将属性配置设置到list中,通过get方法获取该list
Bindable.setOf(Class<E> elementType) //将属性配置设置到set中,通过get方法获取该set
Bindable.mapOf(Class<K> keyType, Class<V> valueType) //将属性配置设置到map中,通过get方法获取该map
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
- 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 进行。
这里重点看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
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两个成员变量:
- AnnotatedBeanDefinitionReader : 配置类读取
- ClassPathBeanDefinitionScanner : 扫描配置路径上所有Bean
实际上这两个成员变量是在手动启动这个ApplicationContext的时候才会生效,默认spring boot启动的时候并不会去执行,而真正启动是在prepareContext启动的时候通过BeanDefinitionLoader进行load的时候才会去启动reader
ServletWebServerApplicationContext为AnnotationConfigServletWebServerApplicationContext的父类,该上下文重写了onRefresh方法,从而实现内嵌web容器创建
- getServletContext 获取ServletContext,首次为空
- getWebServerFactory 获取 ServletWebServerFactory,默认获取到TomcatServletWebServerFactory
- getWebServer 通过ServletWebServerFactory获取Web容器,默认为TomcatWebServer,此时Web容器已经启动完成
- getSelfInitializer 如下图,getSelfInitializer与selfInitialize的写法为lambda表达式,简化了匿名内部类实现,selfInitialize即为ServletContextInitializer方法中的onStartup实现,被调用的时机是在TomcatWebServer创建的时候被调用
- 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进行
AnnotatedBeanDefinitionReader
主要用于注册Spring Bean,核心逻辑位于doRegisterBean比较简单不细讲,这里有个地方需要注意如下:
执行构造函数的时候通过AnnotationConfigUtils.registerAnnotationConfigProcessors将支持注解配置的前置器进行注册。详细点击查看
ClassPathBeanDefinitionScanner
扫描指定Package下的所有Spring Bean(即带有@Component|@Repository|@Service|@Controller等注解的类)并进行实例化。整个类本身逻辑不太复杂,核心逻辑位于doScan方法中,主要就是实现扫描所有带Spring Bean注解的类,然后进行一系列判断装饰,筛选过滤,最后将对应的BeanDefinition注册到上下文中。
ServletWebServerFactory
ServletWebServerFactory通过ServletWebServerFactoryConfiguration进行配置
Spring Boot官方提供三种内嵌Web容器: Tomcat、Jetty、Undertow,且默认为Tomcat,可通过maven配置启用其他容器,比如启用Jetty:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
EnableAutoConfiguration 运行原理
通过查看上图EnableAutoConfiguration注解可知道以下几点:
- AutoConfigurationPackage : 根据下图中的解释,可以知道如果没有设置basePackages或者basePackageClasses则默认导入AutoConfigurationPackages自身所在的配置类,这就是为啥spring boot启动的时候默认会扫描启动类路径及子路径下的所有Bean等,这里的Registrar继承着ImportBeanDefinitionRegistrar用于直接注册BeanDefinition到上下文中,具体查看ConfigurationClassParser的processImports方法
- AutoConfigurationImportSelector : 继承DeferredImportSelector延迟加载EnableAutoConfiguration配置类,来实现EnableAutoConfiguration的扩展,总体逻辑可能比较绕但实际并不太复杂,读者可以自行调试下主要也就是通过getCandidateConfigurations调用SpringFactoriesLoader来获取配置
Configuration注解原理(核心)
- AnnotatedBeanDefinitionReader 初始化时调用AnnotationConfigUtils.registerAnnotationConfigProcessors方法将ConfigurationClassPostProcessor注册到BeanFactory中
- ApplicationContext刷新时通过调用PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors,调用ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法
- processConfigBeanDefinitions 主要处理逻辑如下:
- 遍历找出所有符合条件的ConfigurationClass
- 新建解析器
- 解析所有ConfigurationClass并校验
- 新建ConfigurationClass BeanDefinition Reader,通过Reader解析Configuration类
- postProcessBeanFactory->enhanceConfigurationClasses 中实现Configuration类增强,具体逻辑查看 ConfigurationClassEnhancer
接下来重点分析以上几个核心步骤内部逻辑:
ConfigurationClassUtils.checkConfigurationClassCandidate
主要逻辑就是通过获取 注解元数据 ,并且判断是否有@Configuration注解
关于AnnotationMetadata.introspect写法解析:AnnotationMetadata是一个接口,然而我们却可以直接调用该接口的方法而不需要new实现类,该方法实现直接写在接口上而且标注为static, 该特性为jdk1.8新增加的,与之一起的还有default实现,详细如下:
default boolean hasMetaAnnotation(String metaAnnotationName) {
return getAnnotations().get(metaAnnotationName,
MergedAnnotation::isMetaPresent).isPresent();
}
static AnnotationMetadata introspect(Class<?> type) {
return StandardAnnotationMetadata.from(type);
}
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注解支持,如下:
public interface DemoInterface {
@Bean
default String hello1() {
return "hello1";
};
}
@Configuration
public class DemoA implements DemoInterface {
}
- 还有最重要一点是嵌套子类中也同样支持以上所有特性,doProcessConfigurationClass这个方法里面就是通过递归的方式来遍历所有层次类结构,然后对符合条件的Bean进行注册等。
- 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
解析逻辑如下:
判断自身是否通过其他Configuration通过注解@Import导入的,或者是其他Configuration类的内嵌配置类,若是则将自己注册到上下文中
- 扫描所有带@Bean的方法并进行实例化
- 扫描所有@ImportResource注解对应的xml配置文件,并进行相应实例化
扫描所有@Import注解中的ImportBeanDefinitionRegistrar接口实现
ConfigurationClassEnhancer
每个Configuration对象都会通过cglib进行增强,从而与其他Spring Bean一样支持AOP等功能,而ConfigurationClassEnhancer真是增强工具类,内部通过调用Enhancer来实现增强(关于Enhancer后续单独拿出来解析),接下来我们着重分析ConfigurationClassEnhancer这里面的核心逻辑。
这里有三个增强回调方法,分别作用如下:BeanMethodInterceptor :核心拦截器,作用于@Bean注解的方法
- BeanFactoryAwareMethodInterceptor : 主要是为了兼容BeanFactoryAware中的setBeanFactory,若检查到有这个方法直接跳过拦截调用原始的setBeanFactory方法
- 其他情况则由NoOp.INSTANCE默认响应
为啥需要后面这两个拦截器?
以下是一个简单的跨域配置,只有一个corsFilter方法,而实际上真正扫描出来的方法是有6个的,如下图:
总共多了equals、clone、setBeanFactory、toString、hashCode,除了setBeanFactory其他几个比较好理解的,这几个方法属于Object默认自带的方法只要是Java的对象就会有这几个方法,而setBeanFactory又是哪来的呢,那是因为所有的Configuration类增强后都会继承一个EnhancedConfiguration接口,该接口继承自BeanFactoryAware,对此所有的Configuration类都必须实现一个setBeanFactory方法
这里就引申了另一个问题为啥需要继承这个BeanFactoryAware接口?
这里有几个关键类:
- BeanFactoryAwareGeneratorStrategy
- BeanFactoryAwareMethodInterceptor
- BeanMethodInterceptor
这几个类通过Spring 的 Enhancer工具类串联在一起来实现@Bean方法的拦截,而BeanFactory这个成员变量就是其中的关键,通过BeanFactory去获取实际的一些Bean。接下来分析这几个类的作用
BeanFactoryAwareGeneratorStrategy实现为增强类添加一个成员变量$$beanFactory,我们看下实际的增强效果
备注:可以通过设置System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, “D:\cglib”);来开启cglib文件输出便于我们了解内部结构。
还有我们稍微解释下上面这个增强类的几个成员变量的意义:
- $$beanFactory : 这个上面已经有提到,主要就是存放当前上下文
- CGLIB$CALLBACK_0 :主要存放的是BeanMethodInterceptor这个拦截器
- CGLIB$CALLBACK_1 :主要存放的是BeanFactoryAwareMethodInterceptor这个拦截器
接着上面继续讲,由BeanFactoryAwareGeneratorStrategy声明的beanFactory成员变量将会被BeanFactoryAwareMethodInterceptor进行赋值,如下:
增强类CorsConfigEnhancerBySpringCGLIB494xxx实现了setBeanFactory,当Spring启动时调用setBeanFactory进行赋值时,实际上会代理调用BeanFactoryAwareMethodInterceptor这个类,然后将当前上下文赋值给beanFactory。
所以到这里$$beanFactory实际已经赋值完成,接下来就是如何使用这个成员变量,而该逻辑主要就是在BeanMethodInterceptor中。BeanMethodInterceptor我们单独哪里分析,查看BeanMethodInterceptor
BeanMethodInterceptor
这里的逻辑主要分成以下几种情况:
- 判断是否为FactoryBean,如果是则通过enhanceFactoryBean调用对应getObject方法获取目标bean
- 这里有个判断逻辑,我按照注释尝试翻译以下:isCurrentlyInvokedFactoryMethod方法判断当前@Bean对应方法名是否已经被执行实例化话过,比较逻辑是通过方法名与参数类型进行比较,这里不比较返回值是因为为了兼容covariant return type(协变返回值类型,黑人问号?????),我们这里还是解释下什么是covariant return type:重写基类中的方法,且返回值是原方法返回值类型的继承类型,具体例子可以百度下。若第一次执行则通过cglib动态代理调用对应方法进行实例化
- 最后这个就是补偿逻辑,如果该方法已经实例化了就获取该实例返回即可。
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实现类,该类主要实现了过滤逻辑
```java public interface Condition {boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
主要过滤触发时机
这里我们先介绍ConditionEvaluator这个类,这个类提供一个shouldSkip方法来根据传进去的注解来判断是否需要跳过,而ConditionEvaluator内部实际就是调用Condition进行过滤。
触发时机我们这里主要列举以下几种:
- 解析Configuration类之前发生,通过调试我们可以知道过滤触发时机是在ConfigurationClassParser在执行processConfigurationClass方法解析Configuration之前发生,如下图:
- 解析@Bean方法之前发生,通过ConfigurationClassBeanDefinitionReader的loadBeanDefinitionsForBeanMethod方法加载BeanDefinition之前
- 扫描@Component时发生,ClassPathScanningCandidateComponentProvider的scanCandidateComponents方法在扫描到@Component时,通过查看该类上是否带有@ConditionOnXXXX注解,若有则判断是否符合条件,若无则默认不跳过
AnnotationMetadata
TODO