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()

 

posted on 2020-04-16 21:42  快鸟  阅读(2432)  评论(0编辑  收藏  举报