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

  • 时间:2020-11-08 00:43 作者:凡欲不凡 来源: 阅读:59
  • 扫一扫,手机访问
摘要:SpringBoot 版本 : 2.2.1.RELEASE入口类: SpringApplication;SpringApplicationBuilder说明 : 因为SpringBoot建立在Spring之上,所以分析SpringBoot的启动过程其实与Spring是交错进行的,分析的时候会顺带将一

SpringBoot 版本 : 2.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类型的配置类信息

文章要是勘误或者者知识点说的不正确,欢迎评论,毕竟这也是作者通过阅读源码取得的知识,难免会有疏忽!

  • 全部评论(0)
最新发布的资讯信息
【系统环境|】极客时间-数据分析实战45讲【完结】(2021-09-02 16:26)
【系统环境|windows】字节跳动前台面试题解析:盛最多水的容器(2021-03-20 21:27)
【系统环境|windows】DevOps敏捷60问,肯定有你想理解的问题(2021-03-20 21:27)
【系统环境|windows】字节跳动最爱考的前台面试题:JavaScript 基础(2021-03-20 21:27)
【系统环境|windows】JavaScript 的 switch 条件语句(2021-03-20 21:27)
【系统环境|windows】解决 XML 数据应用实践(2021-03-20 21:26)
【系统环境|windows】20个编写现代CSS代码的建议(2021-03-20 21:26)
【系统环境|windows】《vue 3.0探险记》- 运行报错:Error:To install them, you can run: npm install --save core-js/modules/es.arra...(2021-03-20 21:24)
【系统环境|windows】浅谈前台可视化编辑器的实现(2021-03-20 21:24)
【系统环境|windows】产品经理入门迁移学习指南(2021-03-20 21:23)
血鸟云
手机二维码手机访问领取大礼包
返回顶部