SpringBoot启动 源码深度解析(三)

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中执行处理逻辑为

    1. 👍👍👍获取已经注册的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生成策略。
    2. 👍👍👍下面开始正式解析被@Configuration或者普通注解标注的类:创建配置类解析器ConfigurationClassParser实例构造器会将ComponentScanAnnotationParser 解析器对象一起创建,用于解析@ComponentScan),接着调用org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)解析方法,根据bean定义的不同,创建不同的ConfigurationClass实例对象然后统一调用processConfigurationClass方法做循环解析处理
      1. 通过conditionEvaluator判断配置阶段类型是ConfigurationPhase.PARSE_CONFIGURATION的配置类是否带有@Conditional注解,并做解析校验,判断是否需要跳过.
      2. 从缓存获取当前配置类是否被导入,若导入并且当前的配置类也被导入,则将当前的配置类添加到缓存的配置类中进行合并,若当前配置类没有被导入,则将旧的配置类从缓存中移除,目的是对配置类进行校验。
      3. 👍👍👍👍调用org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass方法中,首先判断配置类有没有被@Component标注(包括@Configuration),有的话,调用org.springframework.context.annotation.ConfigurationClassParser#processMemberClasses 首先处理嵌套的成员类类中套类示例
        image.png
      4. 👍👍👍👍处理@PropertySource注解,通过AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class,PropertySource.class)获取元数据标注的所有@PropertySource注解,然后调用org.springframework.context.annotation.ConfigurationClassParser#processPropertySource去处理每个propertySource包括解析占位符然后将属性添加到propertySourceNames集合中。
      5. 👍👍👍👍处理@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) 实例 ),代码如下:
        image.png

        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集合**。
        image.png
      6. 👍👍👍👍👍处理@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.AutoConfigurationImportSelectorselectImports实现,会将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类做示例👍,代码如下:
        image.png

        首先根据被注解类的注解元数据获取@EnableDubboConfig的注解类型,取到属性multiple对应的值,先把注册单个bean若属性值为true,再注册多个bean。如果既不是ImportSelector类型也不是ImportBeanDefinitionRegistrar类型,则将资源配置元数据添加到importStack中,调用processConfigurationClass(candidate.asConfigClass(configClass))代码至此@Import注解解析完毕
      7. 👍👍👍处理@ImportResource注解,同样的方式获取ImportResource注解的属性值,注解属性不为空,获取对应属性locations、reader的值先处理占位符,再将设置的BeanDefinitionReader添加到缓存 Map<String, Class<? extends BeanDefinitionReader>> importedResources = new LinkedHashMap<>()
      8. 👍👍👍👍处理单独的@Bean methods获取被注释@Bean的方法,若存在并且元数据注解是StandardAnnotationMetadata类型则尝试使用ASM读取并推断声明顺序但是由于JVM的反射机制返回的方法是任意的顺序),若解析出来的bean方法不为空遍历并添加到配置类成员 Set<BeanMethod> beanMethods = new LinkedHashSet<>()中缓存起来。
      9. 👍👍👍👍处理接口的 default 方法对应的@Bean方法,获取元数据class,然后获取class实现的所有接口对应的@Bean方法,添加到configClass配置信息中。跟上一步的区别是这一步获取的是接口对应的@Bean方法
      10. 最后将所有的配置类信息存入 org.springframework.context.annotation.ConfigurationClassParser#configurationClasses缓存中供使用
        image.png
        解析完配置类,最后调用 this.deferredImportSelectorHandler.process(),此handle的作用就是延迟创建配置类

    👍👍总结获取已经注册的bean定义 -> 处理带有注解得嵌套类(member)-> @PropertySources -> @ComponentScan -> @Import -> @ImportResource -> @Bean -> Java8特性 Default方法 -> 是否有父类并且父类不能是Java开头的 -> 解析完会返回null,否则会循环解析。👍👍

  • 👍👍👍👍👍解析完成之后对配置类做校验,1. 如果含有@Configuration注解的配置类不能是final修饰 2. 如果配置类是静态的,则直接返回校验通过,若配置类标有@Configuration,但是不允许覆盖(继承、重写方法),抛出异常,因为spring会对配置类使用CGLIB代理

  • 👍👍👍👍👍接着调用org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions( Set<ConfigurationClass> configClasses )将当前配置类执行注册bean定义。调用org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass方法,如下:

    image.png

    1. 判断配置类是否需要跳过,若需要跳过,则移除已经注册的bean定义和ImportStack中的缓存。然后判断配置类是否已经被Import了,是则调用org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#registerBeanDefinitionForImportedConfigurationClass方法注册当前配置类
    image.png

    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定义,然后进行如下判断
    image.png

    判断当前方法元数据是否是静态的,设置beanClassName为配置类的名称,设置工厂方法名为当前静态方法;若不是静态的,则设置工厂名称为配置类的名称,设置唯一的工厂方法名称为当前@Bean的方法名设置当前bean定义的自动注入模式为构造器注入,设置RequiredAnnotationBeanPostProcessor.skipRequiredCheck跳过@Required检查属性的值为true然后再判断当前元数据是否包含通用的bean定义注解@Lazy、@Primary、@Role、@DependsOn、@Description若存在将属性设置到bean定义中再把@Bean注解其他属性值填充到bean定义中
    获取当前方法对应的@Scope注解的属性紧接着判断是否需要启动代理模式,若需要根据具体的代理方式创建出代理bean定义,然后注册到bean工厂中
    image.png

    4. 加载配置类的所有导入的importSources,执行遍历。校验BeanDefinitionReader.class是否与缓存的Class类型相同,相同的话,在java里面会把readerClass设置为XmlBeanDefinitionReader.class专门用来解析XML的BeanDefinetionReader实现。然后判断缓存中是否存在当前readerClass,不存在的话反射创建一个reader对象,同时设置资源加载器为当前资源加载器实例this.resourceLoader,设置上下文环境this.environment然后放到缓存中。最后调用reader.loadBeanDefinitions(resource)代码去加载bean定义
    image.png

    5. 加载配置类的所有导入的ImportBeanDefinetionRegistrars遍历registrars然后调用registrar的registerBeanDefinitions方法,实现所有子类的自定义回调

总结:至此,ConfigurationClassPostProcessor后置处理器校验beanDefinetion、解析beanDefinetion、加载beanDefinetion就完成了
注册流程为校验是否需要跳过 -> @Import导入或者是嵌套类的配置类信息 -> @Bean方法配置信息注册 -> @ImportResource配置类信息 -> ImportBeanDefinetionRegistrar类型的配置类信息

  1. ☛ 文章要是勘误或者知识点说的不正确,欢迎评论,毕竟这也是作者通过阅读源码获得的知识,难免会有疏忽!
  2. 要是感觉文章对你有所帮助,不妨点个关注,或者移驾看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
  3. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343