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