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 { ...... }
// 处理 @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 }
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()