SpringBoot 版本 : 2.2.1.RELEASE
Spring 版本 : 5.2.1.RELEASE
入口类: SpringApplication;SpringApplicationBuilder
说明 : 由于SpringBoot建立在Spring之上,所以分析SpringBoot的启动过程其实与Spring是交错进行的,分析的时候会顺带将一些Spring的扩展点也提到
注:本文主要讲解一些比较重要的关键步骤,不能面面俱到,若有疑问,随时保持沟通
SpringBoot启动 源码深度解析(一)
SpringBoot启动 源码深度解析(二)
SpringBoot启动 源码深度解析(四)
-
下面来看核心的配置类处理器ConfigurationClassPostProcessor流程:进入到: org.springframework.context.annotation.ConfigurationClassPostProcessor
#postProcessBeanDefinitionRegistry方法中:当前后置处理器的作用是解析bean定义配置类,实现IOC,
。进到方法org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions中执行处理逻辑为:- 👍👍👍获取已经注册的bean名称,根据名称获取所有的bean定义,循环判断当前bean的属性是否是全量( 属性名称为
ConfigurationClassPostProcessor.configurationClass
对应的属性值为full
)或者是轻量的( 属性名称为ConfigurationClassPostProcessor.configurationClass
对应的属性值为lite
),若不满足再对当前bean定义做checkConfigurationClassCandidate类型检查,方法中首先会检查是否是AnnotatedBeanDefinition实例并且class名称与元数据中缓存的class名称要相同才会重用bean定义中的元数据。最后若重新生成的元数据包含@configuration注解,那么设置属性为full,若包含的属性包含@Component、@Bean、@Import、@ImportSource、@ComponentScan,设置属性为lite并返回true。那么此时会将当前bean定义添加到configCandidates集合中,然后获取当前bean定义的ConfigurationClassPostProcessor.order属性的数值进行排序。然后判断是否有自定义的单例bean生成策略。 - 👍👍👍下面开始正式解析被@Configuration或者
普通注解
标注的类:创建配置类解析器ConfigurationClassParser实例
(构造器会将ComponentScanAnnotationParser 解析器对象一起创建,用于解析@ComponentScan),接着调用org.springframework.context.annotation.ConfigurationClassParser
#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)解析方法,根据bean定义的不同,创建不同的ConfigurationClass实例对象
,然后统一调用processConfigurationClass方法做循环解析处理: -
通过conditionEvaluator判断配置阶段类型是
ConfigurationPhase.PARSE_CONFIGURATION
的配置类是否带有@Conditional注解,并做解析校验,判断是否需要跳过. - 从缓存获取当前配置类是否被导入,若导入并且当前的配置类也被导入,则将当前的配置类添加到缓存的配置类中进行合并,若当前配置类没有被导入,则将旧的配置类从缓存中移除,目的是对配置类进行校验。
- 👍👍👍👍调用
org.springframework.context.annotation.ConfigurationClassParser
#doProcessConfigurationClass方法中,首先判断配置类有没有被@Component标注(包括@Configuration),有的话,调用org.springframework.context.annotation.ConfigurationClassParser
#processMemberClasses 首先处理嵌套的成员类(类中套类示例)
- 👍👍👍👍处理@PropertySource注解,通过AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class,PropertySource.class)获取元数据标注的所有@PropertySource注解,然后调用
org.springframework.context.annotation.ConfigurationClassParser
#processPropertySource去处理每个propertySource
。包括解析占位符、然后将属性添加到propertySourceNames集合中。 - 👍👍👍👍处理@ComponentScan注解,获取方式与上面一样,若获取的集合不为空并且当前条件判断
ConfigurationPhase.REGISTER_BEAN
的注册bean阶段不会跳过流程,则依次遍历所有的结果集,调用org.springframework.context.annotation.ComponentScanAnnotationParser
#parse立刻执行扫描过程,解析器会将@ComponentScan注解属性解析到ClassPathBeanDefinitionScanner对象中
。ClassPathBeanDefinitionScanner(会扫描@Component、@Repository、@Service、@Controller、javax.annotation.ManagedBean、javax.inject.Named 等注解)。 然后调用org.springframework.context.annotation.ClassPathBeanDefinitionScanner
#doScan( StringUtils.toStringArray(basePackages) )** 开始进行扫描处理。接着调用org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
#findCandidateComponents( String basePackage ) 在当前路径下查找候选components。解析basePackage 的占位符和转换包名对应的 · 为 / ,即:
// @ComponentScan("com.ljj.sourcecode.analysis")包路径处理:
// packageSearchPath = classpath*:com/ljj/sourcecode/analysis/**/*.class
然后会解析当前路径下的所有资源(包括依赖的jar)。然后调用isCandidateComponent(metadataReader) 判断是否是候选的组件( 在ClassPathBeanDefinitionScanner初始化的时候,会往 this.includeFilters 集合中添加一个new AnnotationTypeFilter(Component.class) 实例
),代码如下:
org.springframework.context.annotation.ClassPathScanningCandidateCompon entProvider#isConditionMatch 判断是否需要跳过,返回boolean结果。最
后创建ScannedGenericBeanDefinition实例化对象。再次判断是否是候选components,
若是的话将ScannedGenericBeanDefinition实例添加到candidates集合中,循环完毕返
回candidates集合.遍历筛选出来的BeanDefinition,
接着执行
this.scopeMetadataResolver.resolveScopeMetadata(candidate) 获取属性元数据;
创建bean名称若bean定义是AbstractBeanDefinition者执行
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#postProcessBeanDefinition方法处理生成的bean定义,如果不设置成员
autowireCandidatePatterns的值,则设置bean定义的autowireCandidate为false。 > 若bean定义是AnnotatedBeanDefinition**的实例,需要执行代码 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate)处理注解为 @Lazy、@Primary、@DependsOn、@Role、@Description
,解析到bean定义中。然后再调用org.springframework.context.annotation.ClassPathBeanDefinitionScanner#checkCandidate方法判断bean定义是否已经被注册,若没有注册,进行后续的bean定义创建流程,若已经注册 则返回false或者抛出bean冲突异常。遍历结束返回所有的 beanDefinitions集合**。 - 👍👍👍👍👍处理@Import注解,执行代码
processImports(configClass, sourceClass, getImports(sourceClass), true)
,先递归的获取所有注解带有的@Import,然后参数传入到org.springframework.context.annotation.ConfigurationClassParser
#processImports方法中。进入当前方法之后,首先对导入候选注解做一个非空判断,集合为空直接结束@Import处理,再根据传入的参数checkForCircularImports
判断如果启动循环导入检查(true)并且被放入导入栈中(importStack
)则结束处理;否则开始解析所有的importCandidates集合。若导入候选类型为ImportSelector
,则实例化当前ImportSelector
实例,同时会判断当前实例是否是Aware
子类型,若是,则回调具体的Aware子接口(BeanClassLoaderAware、BeanFactoryAware && 是BeanFactory的子类型、EnvironmentAware、ResourceLoaderAware),进一步判断是否是DeferredImportSelector子接口类型
,若是,直接添加到deferredImportSelectors集合中,否则调用当前实例的selectImports
方法,执行自定义的导入处理。比如boot中自动装配功能org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
的selectImports实现,会将spring.factories中的所有EnableAutoConfiguration.class的实现筛选出来。接下来的递归操作也正是AutoConfigurationImportSelector
这类筛选器的操作体现,因为执行完筛选之后,可能生成很多@Configuration类。另一种情况,若是ImportBeanDefinitionRegistrar类型的,依然是先执行这行回调代码ParserStrategyUtils.invokeAwareMethods( registrar, this.environment, this.resourceLoader, this.registry)
。然后向
Map<ImportBeanDefinitionRegistrar, AnnotationMetadata>importBeanDefinitionRegistrars
= new LinkedHashMap<>()成员遍历中添加当前beanDefinetionRegistrar
。👍这里用org.apache.dubbo.config.spring.context.annotation.DubboConfigConfigurationRegistrar类做示例👍,代码如下:
首先根据被注解类的注解元数据获取@EnableDubboConfig的注解类型,取到属性multiple
对应的值,先把注册单个bean,若属性值为true,再注册多个bean。如果既不是ImportSelector类型也不是ImportBeanDefinitionRegistrar类型,则将资源配置元数据添加到importStack中,调用processConfigurationClass(candidate.asConfigClass(configClass))代码,至此@Import注解解析完毕。 - 👍👍👍处理@ImportResource注解,
同样的方式获取ImportResource注解的属性值,注解属性不为空,获取对应属性locations、reader的值
,先处理占位符,再将设置的BeanDefinitionReader添加到缓存Map<String, Class<? extends BeanDefinitionReader>> importedResources = new LinkedHashMap<>()
中。 - 👍👍👍👍处理单独的@Bean methods,
获取被注释@Bean的方法,若存在并且元数据注解是StandardAnnotationMetadata类型
,则尝试使用ASM读取并推断声明顺序(但是由于JVM的反射机制返回的方法是任意的顺序),若解析出来的bean方法不为空遍历并添加到配置类成员 Set<BeanMethod>beanMethods
= new LinkedHashSet<>()中缓存起来。 - 👍👍👍👍处理接口的 default 方法对应的@Bean方法,获取元数据class,然后获取class实现的所有接口对应的@Bean方法,添加到configClass配置信息中。
跟上一步的区别是这一步获取的是接口对应的@Bean方法
。 -
最后将所有的配置类信息存入
org.springframework.context.annotation.ConfigurationClassParser
#configurationClasses缓存中供使用
。
-
通过conditionEvaluator判断配置阶段类型是
👍👍总结: 获取已经注册的bean定义 -> 处理带有注解得嵌套类(member)-> @PropertySources -> @ComponentScan -> @Import -> @ImportResource -> @Bean -> Java8特性 Default方法 -> 是否有父类并且父类不能是Java开头的 -> 解析完会返回null,否则会循环解析。👍👍
- 👍👍👍获取已经注册的bean名称,根据名称获取所有的bean定义,循环判断当前bean的属性是否是全量( 属性名称为
👍👍👍👍👍解析完成之后对配置类做校验,
1. 如果含有@Configuration注解的配置类不能是final修饰 2. 如果配置类是静态的,则直接返回校验通过,若配置类标有@Configuration,但是不允许覆盖(继承、重写方法),抛出异常,因为spring会对配置类使用CGLIB代理
。-
👍👍👍👍👍接着调用
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
#loadBeanDefinitions( Set<ConfigurationClass> configClasses )将当前配置类执行注册bean定义。调用org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
#loadBeanDefinitionsForConfigurationClass方法,如下:
1. 判断配置类是否需要跳过,若需要跳过,则移除已经注册的bean定义和ImportStack中的缓存。然后判断配置类是否已经被Import了,是则调用org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
#registerBeanDefinitionForImportedConfigurationClass方法注册当前配置类:
2. 若当前配置类是通过@Import进来的或者是嵌套类,首先获取配置类的注解元数据,创建一个注解普通beanDefinetion对象 AnnotatedGenericBeanDefinition,通过成员属性 ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver()解析当前配置类的@Scope注解属性,获取属性值添加到beanDefinetion中
(默认单例)。然后处理通用的bean定义注解(包括 @Lazy、@Primary、@DependsOn、@Role、@Description)封装到bean定义中。最后创建BeanDefinitionHolder对象,根据前面解析的scope属性判断是否需要做代理
,通过BeanDefinitionRegistry
注册器将bean定义注册到bean工厂中。
3. 加载配置类的@Bean方法相关配置,迭代调用org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
#loadBeanDefinitionsForBeanMethod方法加载bean定义。方法篇幅过大,大致流程是:判断是否需要跳过注册,然后获取@Bean的属性值,为名称注册别名
;接着判断是否允许存在的bean定义被覆盖,若允许此处会提前结束bean创建,不允许就开始创建配置类bean定义,然后进行如下判断:
判断当前方法元数据是否是静态的,设置beanClassName为配置类的名称,设置工厂方法名为当前静态方法;若不是静态的,则设置工厂名称为配置类的名称,设置唯一的工厂方法名称为当前@Bean的方法名。设置当前bean定义的自动注入模式为构造器注入,设置RequiredAnnotationBeanPostProcessor.skipRequiredCheck
跳过@Required检查属性的值为true。然后再判断当前元数据是否包含通用的bean定义注解:@Lazy、@Primary、@Role、@DependsOn、@Description
,若存在将属性设置到bean定义中,再把@Bean注解其他属性值填充到bean定义中。
获取当前方法对应的@Scope注解的属性,紧接着判断是否需要启动代理模式,若需要根据具体的代理方式创建出代理bean定义,然后注册到bean工厂中
。
4. 加载配置类的所有导入的importSources,执行遍历。校验BeanDefinitionReader.class是否与缓存的Class类型相同,相同的话,在java里面会把readerClass设置为XmlBeanDefinitionReader.class
,专门用来解析XML的BeanDefinetionReader实现。然后判断缓存中是否存在当前readerClass,不存在的话反射创建一个reader对象,同时设置资源加载器为当前资源加载器实例this.resourceLoader
,设置上下文环境this.environment
然后放到缓存中。最后调用reader.loadBeanDefinitions(resource)代码去加载bean定义。
5. 加载配置类的所有导入的ImportBeanDefinetionRegistrars。遍历registrars然后调用registrar的registerBeanDefinitions方法,实现所有子类的自定义回调
。
总结:至此,ConfigurationClassPostProcessor后置处理器中
校验beanDefinetion、解析beanDefinetion、加载beanDefinetion就完成了
。
注册流程为: 校验是否需要跳过 -> @Import导入或者是嵌套类的配置类信息 -> @Bean方法配置信息注册 -> @ImportResource配置类信息 -> ImportBeanDefinetionRegistrar类型的配置类信息。
- ☛ 文章要是勘误或者知识点说的不正确,欢迎评论,毕竟这也是作者通过阅读源码获得的知识,难免会有疏忽!
- ☛ 要是感觉文章对你有所帮助,不妨点个关注,或者移驾看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
- ☛ 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处!