zoukankan      html  css  js  c++  java
  • SpringBoot Bean 扫描注册核心之:ConfigurationClassPostProcessor 详解

    (SpringBoot 版本:2.2.2.RELEASE)

    可以说 @Configuration 是 SpringBoot 配置的基石,自然 @Configuration 类的处理是很有必要研究的。

    @Configuration 类的处理是由 ConfigurationClassPostProcessor 来处理的。

    以如下工程结构来分析:

    问题驱动:

    ServiceA 在按条件加载时(@ConditionalOnBean)有时生效,有时不生效,为了研究原因,新建了如上一个简单工程来模拟场景。其中 Config1,Config3,Config4,Config5 都是 @Import 进来的,ServiceB 以 @Bean 的方式定义在 Config4 中,Config6, ServiceC 都是由 Config5 上的 @ComponentScan("com.kvn.other2") 加载的

    得到如下结果:

    //@ConditionalOnBean(Config1.class)
    //@ConditionalOnBean(Config2.class) // 在 PlainApplication 所在的包(或子包)下面的 bean,可以正常使用 @ConditionalOnBean
    //@ConditionalOnBean(Config3.class)
    //@ConditionalOnBean(ServiceB.class)
    //@ConditionalOnBean(ServiceC.class)  // 在 PlainApplication 所在的包外面,只有 @ComponentScan 这种方式是可以的
    //@ConditionalOnBean(Config5.class)
    @ConditionalOnBean(Config6.class) // 在 PlainApplication 所在的包外面,只有 @ComponentScan 这种方式是可以的
    // 有一点是值得注意的:即使 @ComponentScan 扫描 Config5 类自身,@ConditionalOnBean(Config5.class) 条件也为失败
    // 但是其他 @Configuration 类做为条件是可以的
    @Service
    public class ServiceA {
    ......
    }
    View Code

    // 处理 @Configuration 类的导入,将 bean 注册到容器中
    org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions

      1 public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
      2     List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
      3     String[] candidateNames = registry.getBeanDefinitionNames();
      4 
      5     // 1. 检索所有的配置类
      6     for (String beanName : candidateNames) {
      7         BeanDefinition beanDef = registry.getBeanDefinition(beanName);
      8         if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
      9             if (logger.isDebugEnabled()) {
     10                 logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
     11             }
     12         }
     13         else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
     14             configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
     15         }
     16     }
     17 
     18     // Return immediately if no @Configuration classes were found
     19     if (configCandidates.isEmpty()) {
     20         return;
     21     }
     22 
     23     // Sort by previously determined @Order value, if applicable
     24     // 2. 对所有的配置类进行排序。(按 @Order 的顺序排,@Configuration 类的顺序决定了后面的处理顺序)
     25     configCandidates.sort((bd1, bd2) -> {
     26         int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
     27         int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
     28         return Integer.compare(i1, i2);
     29     });
     30 
     31     // Detect any custom bean name generation strategy supplied through the enclosing application context
     32     SingletonBeanRegistry sbr = null;
     33     if (registry instanceof SingletonBeanRegistry) {
     34         sbr = (SingletonBeanRegistry) registry;
     35         if (!this.localBeanNameGeneratorSet) {
     36             BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
     37                     AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
     38             if (generator != null) {
     39                 this.componentScanBeanNameGenerator = generator;
     40                 this.importBeanNameGenerator = generator;
     41             }
     42         }
     43     }
     44 
     45     if (this.environment == null) {
     46         this.environment = new StandardEnvironment();
     47     }
     48 
     49     // Parse each @Configuration class
     50     ConfigurationClassParser parser = new ConfigurationClassParser(
     51             this.metadataReaderFactory, this.problemReporter, this.environment,
     52             this.resourceLoader, this.componentScanBeanNameGenerator, registry);
     53 
     54     Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
     55     Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
     56     do {
     57         // 3. 解析所有的配置类,其中包括 @PropertySource、@ComponentScan、@Import、@ImportResource、@Bean 等的处理,这些 bean 全部都会被封装成一个 ConfigurationClass 实例,包括普通 bean 和 @Configuration 标记的类
     58         // 需要注意的是,SpringBoot 的启动类(这里指:PlainApplication)是最先开始解析的
     59         // 特别需要注意的是:@ComponentScan 扫描出来的 bean 会在这一步中优先注册到容器中(DefaultListableBeanFactory#beanDefinitionMap)
     60         // 有些 bean (比如:serviceA)是带条件注册的(指:@ConditionalOnXxx),比如条件是 @ConditionalOnBean(x.y),则有可能达不到预期
     61         // 达不到预期的场景:x.y 这个 bean 不是 @ComponentScan 扫描出来的 bean,而是通过 @Import、@ImportResource、@Bean 等方式产生的,则 serviceA 不会被注册到容器中。
     62         // 那么,如何修正或者规避这类问题呢?答案是将 x.y 这个 bean 使用 @ComponentScan 的方式扫描进行注册
     63         parser.parse(candidates);
     64         parser.validate();
     65 
     66         Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
     67         configClasses.removeAll(alreadyParsed);
     68 
     69         // Read the model and create bean definitions based on its content
     70         if (this.reader == null) {
     71             this.reader = new ConfigurationClassBeanDefinitionReader(
     72                     registry, this.sourceExtractor, this.resourceLoader, this.environment,
     73                     this.importBeanNameGenerator, parser.getImportRegistry());
     74         }
     75         // 4. 将所有的 ConfigurationClass 实例时行加载,即将定义的 bean 注册到容器中
     76         // a. 判断是否要 skip
     77         // b. 处理 import 导入的 @Configuration 类,将它注册到容器中。beanName 默认为类的全限定名,如:com.kvn.other.Config1
     78         // c. 将 ConfigurationClass 中的 BeanMethod 类型的 bean(即 @Bean 标记的方法)注册到容器中
     79         // d. 将 @ImportResources 中的 bean 注册到容器中
     80         // e. 注册 ImportBeanDefinitionRegistrar 接口中手动注册的 bean
     81         this.reader.loadBeanDefinitions(configClasses);
     82         alreadyParsed.addAll(configClasses);
     83 
     84         candidates.clear();
     85         if (registry.getBeanDefinitionCount() > candidateNames.length) {
     86             String[] newCandidateNames = registry.getBeanDefinitionNames();
     87             Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
     88             Set<String> alreadyParsedClasses = new HashSet<>();
     89             for (ConfigurationClass configurationClass : alreadyParsed) {
     90                 alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
     91             }
     92             for (String candidateName : newCandidateNames) {
     93                 if (!oldCandidateNames.contains(candidateName)) {
     94                     BeanDefinition bd = registry.getBeanDefinition(candidateName);
     95                     if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
     96                             !alreadyParsedClasses.contains(bd.getBeanClassName())) {
     97                         candidates.add(new BeanDefinitionHolder(bd, candidateName));
     98                     }
     99                 }
    100             }
    101             candidateNames = newCandidateNames;
    102         }
    103     }
    104     while (!candidates.isEmpty());
    105 
    106     // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
    107     if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
    108         sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
    109     }
    110 
    111     ......
    112 }
    3. org.springframework.context.annotation.ConfigurationClassParser#parse(Set<BeanDefinitionHolder> configCandidates) // 最先处理 PlainApplication,它本身就是一个 @Configuration class
        3.1 org.springframework.context.annotation.ConfigurationClassParser#parse(..., String beanName)
            3.1.1 org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass()  // 处理 @PropertySource -> @ComponentScan -> @Import -> @ImportResource -> @Bean -> 处理 Java 8+ ConfigurationClass 实现的接口上的 @Bean -> 处理 supper class
                3.1.1.1 org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition // 注册 @ComponentScan 扫描出来的 bean,即 PlainApplication 所在的包(com.kvn.boot)及子包下的 bean
        3.2 org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorHandler#process() // 迟延导入选择器处理,导入通过 PlainApplication 扫描出来的 @Configuration Class
            3.2.1 org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports()
                3.2.1.1 处理 ImportSelector 
                3.2.1.2 处理 ImportBeanDefinitionRegistrar
                3.2.1.3 其他情况,按照 @Configuration 来处理
                    3.2.1.3.1 org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass // 回到 3.1.1 的处理

    3. org.springframework.context.annotation.ConfigurationClassParser#parse(Set<BeanDefinitionHolder> configCandidates)

     1 public void parse(Set<BeanDefinitionHolder> configCandidates) {
     2     for (BeanDefinitionHolder holder : configCandidates) {
     3         BeanDefinition bd = holder.getBeanDefinition();
     4         try {
     5             if (bd instanceof AnnotatedBeanDefinition) {
     6                 parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
     7             }
     8             else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
     9                 parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
    10             }
    11             else {
    12                 parse(bd.getBeanClassName(), holder.getBeanName());
    13             }
    14         }
    15         catch (BeanDefinitionStoreException ex) {
    16             throw ex;
    17         }
    18         catch (Throwable ex) {
    19             throw new BeanDefinitionStoreException(
    20                     "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
    21         }
    22     }
    23     
    24     // 迟延导入选择器处理程序,导入 PlainApplication 扫描出来的 Configuration Class
    25     this.deferredImportSelectorHandler.process();
    26 }
    View Code

    3.1.1 org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass()

     1 protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
     2         throws IOException {
     3 
     4     if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
     5         // Recursively process any member (nested) classes first
     6         processMemberClasses(configClass, sourceClass);
     7     }
     8 
     9     // Process any @PropertySource annotations
    10     // 处理 @PropertySource
    11     for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
    12             sourceClass.getMetadata(), PropertySources.class,
    13             org.springframework.context.annotation.PropertySource.class)) {
    14         if (this.environment instanceof ConfigurableEnvironment) {
    15             processPropertySource(propertySource);
    16         }
    17         else {
    18             logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
    19                     "]. Reason: Environment must implement ConfigurableEnvironment");
    20         }
    21     }
    22 
    23     // Process any @ComponentScan annotations
    24     // 处理 @ComponentScan。@ComponentScan 扫描出来的 bean 会优先注册到容器中。
    25     // 此时,扫描出来的 bean 如果使用条件加载的方式(即:@ConditionalOnXxx)进行注册,则有可能达不到预期。(原因见上面)
    26     Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
    27             sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    28     if (!componentScans.isEmpty() &&
    29             !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
    30         for (AnnotationAttributes componentScan : componentScans) {
    31             // The config class is annotated with @ComponentScan -> perform the scan immediately
    32             // 立马执行 scan 扫描
    33             Set<BeanDefinitionHolder> scannedBeanDefinitions =
    34                     this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
    35             // Check the set of scanned definitions for any further config classes and parse recursively if needed
    36             for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
    37                 BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
    38                 if (bdCand == null) {
    39                     bdCand = holder.getBeanDefinition();
    40                 }
    41                 if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
    42                     // @ComponentScan 扫描出来的 bean 最先注册到容器中
    43                     parse(bdCand.getBeanClassName(), holder.getBeanName());
    44                 }
    45             }
    46         }
    47     }
    48 
    49     // Process any @Import annotations
    50     // 处理 @Import
    51     processImports(configClass, sourceClass, getImports(sourceClass), true);
    52 
    53     // Process any @ImportResource annotations
    54     // 处理 @ImportResource
    55     AnnotationAttributes importResource =
    56             AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    57     if (importResource != null) {
    58         String[] resources = importResource.getStringArray("locations");
    59         Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
    60         for (String resource : resources) {
    61             String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
    62             configClass.addImportedResource(resolvedResource, readerClass);
    63         }
    64     }
    65 
    66     // Process individual @Bean methods
    67     // 处理 @Bean
    68     Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    69     for (MethodMetadata methodMetadata : beanMethods) {
    70         configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    71     }
    72 
    73     // Process default methods on interfaces
    74     // 处理接口中的 @Bean 方法(Java 8+ 支持接口使用默认实现方法)
    75     processInterfaces(configClass, sourceClass);
    76 
    77     // Process superclass, if any
    78     // 处理父类
    79     if (sourceClass.getMetadata().hasSuperClass()) {
    80         String superclass = sourceClass.getMetadata().getSuperClassName();
    81         if (superclass != null && !superclass.startsWith("java") &&
    82                 !this.knownSuperclasses.containsKey(superclass)) {
    83             this.knownSuperclasses.put(superclass, configClass);
    84             // Superclass found, return its annotation metadata and recurse
    85             return sourceClass.getSuperClass();
    86         }
    87     }
    88 
    89     // No superclass -> processing is complete
    90     return null;
    91 }

    4. org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions()
         4.1 org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass()

     1 private void loadBeanDefinitionsForConfigurationClass(
     2         ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
     3 
     4     // a. 判断是否要 skip
     5     if (trackedConditionEvaluator.shouldSkip(configClass)) {
     6         String beanName = configClass.getBeanName();
     7         if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
     8             this.registry.removeBeanDefinition(beanName);
     9         }
    10         this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
    11         return;
    12     }
    13 
    14     // b. 处理 import 导入的 @Configuration 类,将它注册到容器中。beanName 默认为类的全限定名,如:com.kvn.other.Config1
    15     if (configClass.isImported()) {
    16         registerBeanDefinitionForImportedConfigurationClass(configClass);
    17     }
    18     // c. 将 ConfigurationClass 中的 BeanMethod 类型的 bean(即 @Bean 标记的方法)注册到容器中
    19     for (BeanMethod beanMethod : configClass.getBeanMethods()) {
    20         loadBeanDefinitionsForBeanMethod(beanMethod);
    21     }
    22 
    23     // d. 将 @ImportResources 中的 bean 注册到容器中
    24     loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    25     // e. 注册 ImportBeanDefinitionRegistrar 接口中手动注册的 bean
    26     loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    27 }

    附:

    // bean name 生成规则
    1. org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan()
        1.1 org.springframework.beans.factory.support.BeanNameGenerator#generateBeanName()
  • 相关阅读:
    Unable to connect to web server 'IIS Express'(无法连接到Web服务器“IIS Express”)的解决方式-Jexus Manager
    temp_web
    使用Fluent配置表关系
    面试题链接记录
    面试题
    SQL语言基础
    .net core中DbProviderFactories配置问题
    Swagger UI in AspNetCore WebAPI
    JS实现国密算法SM2加密,后端Java解密
    Java读取磁盘指定扇区
  • 原文地址:https://www.cnblogs.com/kevin-yuan/p/12716050.html
Copyright © 2011-2022 走看看