美文网首页程序员spring boot源码解析
SpringBoot启动 源码深度解析(三)

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

作者: 凡毓不凡 | 来源:发表于2020-05-09 01:09 被阅读0次

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类型的配置类信息

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

相关文章

网友评论

    本文标题:SpringBoot启动 源码深度解析(三)

    本文链接:https://www.haomeiwen.com/subject/pdkqnhtx.html