Spring-@Configuration注解简析

前言 Spring中的@Configuration注解修饰的类被称为配置类,通过配置类可以向容器注册bean以及导入其它配置类,本篇文章将结合例子和源码对@Configuration注解原理进行学习,并引出对Spring框架在处理配置类过程中起重要作用的ConfigurationClassPostProcessor的讨论。
Springboot版本:2.4.1
正文 一. @Configuration注解简析
基于@Configuration注解可以实现基于JavaConfig的方式来声明Spring中的bean,与之作为对比的是基于XML的方式来声明bean。由@Configuration注解标注的类中所有由@Bean注解修饰的方法返回的对象均会被注册为Spring容器中的bean,使用举例如下。

@Configuration public class TestBeanConfig {@Bean public TestBean testBean() { return new TestBean(); }}

如上所示,Spring容器会将TestBean注册为Spring容器中的bean。由@Configuration注解修饰的类称为Spring中的配置类,Spring中的配置类在Spring启动阶段会被先加载并解析为ConfigurationClass,然后会基于每个配置类对应的ConfigurationClass对象为容器注册BeanDefinition,以及基于每个配置类中由@Bean注解修饰的方法为容器注册BeanDefinition,后续Spring也会基于这些BeanDefinition向容器注册bean。关于BeanDefinition的概念,可以参见Spring-BeanDefinition简析。
在详细分析由@Configuration注解修饰的配置类是如何被解析为ConfigurationClass以及最终如何被注册为BeanDefinition前,得先探究一下Springboot的启动类,因为后续的分析会以Springboot的启动为基础,所以有必要先了解一下Springboot中的启动类。
Springboot的启动类由@SpringBootApplication注解修饰,@SpringBootApplication注解签名如下所示。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { ...... }

@SpringBootApplication注解的功能主要由@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan实现,后两者与Springboot中的自动装配有关,关于Springboot实现自动装配,会在后续文章中学习,在这里主要关心@SpringBootConfiguration注解。实际上,@SpringBootConfiguration注解其实就是@Configuration注解,@SpringBootConfiguration注解的签名如下所示。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { ...... }

既然@SpringBootConfiguration注解等同于@Configuration注解,那么相应的Springboot的启动类就是一个配置类,Springboot的启动类对应的BeanDefinition会在准备Springboot容器阶段就注册到容器中,将断点打到SpringApplication#run()方法中调用refreshContext()方法这一行代码,而已知refreshContext()这一行代码用于初始化容器,执行到refreshContext()方法时容器已经完成了准备,此时看一下容器的数据,如下所示。
Spring-@Configuration注解简析
文章图片

此时Springboot容器持有的DefaultListableBeanFactory中的beanDefinitionMap中已经存在了Springboot启动类对应的BeanDefinition,在初始化Springboot容器阶段,Springboot启动类对应的BeanDefinition会首先被处理,通过处理Springboot启动类对应的BeanDefinition才会引入对其它配置类的处理。关于Springboot启动类,暂时了解到这里,下面再给出一张处理配置类的调用链,以供后续阅读参考。
Spring-@Configuration注解简析
文章图片

本篇文章后续将从ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry()方法开始,对由@Configuration注解修饰的配置类的处理进行说明。
二. ConfigurationClassPostProcessor处理配置类
通过第一节中的调用链可知,在Springboot启动时,初始化容器阶段会调用到ConfigurationClassPostProcessor来处理配置类,即由@Configuration注解修饰的类。ConfigurationClassPostProcessor是由Spring框架提供的bean工厂后置处理器,类图如下所示。
Spring-@Configuration注解简析
文章图片

可知ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor接口,同时BeanDefinitionRegistryPostProcessor接口又继承于BeanFactoryPostProcessor,所以ConfigurationClassPostProcessor本质上就是一个bean工厂后置处理器。ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor接口定义的postProcessBeanDefinitionRegistry()方法,在ConfigurationClassPostProcessor中对该方法的注释如下。
Derive further bean definitions from the configuration classes in the registry.
直译过来就是:从注册表中的配置类派生进一步的bean定义。那么这里的注册表指的就是容器持有的DefaultListableBeanFactory,而Springboot框架在容器准备阶段就将Springboot的启动类对应的BeanDefinition注册到了DefaultListableBeanFactory的beanDefinitionMap中,所以注册表中的配置类指的就是Springboot的启动类(前文已知Springboot的启动类就是一个配置类),而派生进一步的bean定义,就是将Springboot启动类上@EnableAutoConfiguration@ComponentScan等注解加载的配置类解析为BeanDefinition并注册到DefaultListableBeanFactory的beanDefinitionMap中。暂时不清楚在Springboot启动流程中,ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry()方法注释中提到的配置类是否会有除了Springboot启动类之外的配置类,欢迎留言讨论。
即现在知道,ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry()方法主要处理目标就是Springboot的启动类,通过处理Springboot启动类引出对其它配置类的处理,下面跟随源码,进行学习。postProcessBeanDefinitionRegistry()方法如下所示。
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { int registryId = System.identityHashCode(registry); if (this.registriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry); } if (this.factoriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + registry); } //记录已经处理过的注册表id this.registriesPostProcessed.add(registryId); processConfigBeanDefinitions(registry); }

postProcessBeanDefinitionRegistry()方法会记录已经处理过的注册表id,防止同一注册表被重复处理。实际的处理逻辑在processConfigBeanDefinitions()中,由于processConfigBeanDefinitions()方法比较长,所以这里先把processConfigBeanDefinitions()方法的处理流程进行一个梳理,如下所示。
  • 先把Springboot启动类的BeanDefinition从注册表(这里指DefaultListableBeanFactory,后续如果无特殊说明,注册表默认指DefaultListableBeanFactory)的beanDefinitionMap中获取出来;
  • 创建ConfigurationClassParser,解析Springboot启动类的BeanDefinition,即解析@PropertySource@ComponentScan@Import@ImportResource@Bean等注解并生成ConfigurationClass,最后缓存在ConfigurationClassParser的configurationClasses中;
  • 创建ConfigurationClassBeanDefinitionReader,解析所有ConfigurationClass,基于ConfigurationClass创建BeanDefinition并缓存到注册表的beanDefinitionMap中。
processConfigBeanDefinitions()方法源码如下。
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List configCandidates = new ArrayList<>(); String[] candidateNames = registry.getBeanDefinitionNames(); //从注册表中把Springboot启动类对应的BeanDefinition获取出来 for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } }//如果未获取到Springboot启动类对应的BeanDefinition,则直接返回 if (configCandidates.isEmpty()) { return; }configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); SingletonBeanRegistry sbr = null; if (registry instanceof SingletonBeanRegistry) { sbr = (SingletonBeanRegistry) registry; if (!this.localBeanNameGeneratorSet) { BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton( AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR); if (generator != null) { this.componentScanBeanNameGenerator = generator; this.importBeanNameGenerator = generator; } } }if (this.environment == null) { this.environment = new StandardEnvironment(); }//创建ConfigurationClassParser以解析Springboot启动类及其引出的其它配置类 ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set candidates = new LinkedHashSet<>(configCandidates); Set alreadyParsed = new HashSet<>(configCandidates.size()); do { StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse"); //ConfigurationClassParser开始执行解析 parser.parse(candidates); parser.validate(); //将ConfigurationClassParser解析得到的ConfigurationClass拿出来 Set configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); //创建ConfigurationClassBeanDefinitionReader,以基于ConfigurationClass创建BeanDefinition if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } //开始创建BeanDefinition并注册到注册表中 this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end(); candidates.clear(); if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames)); Set alreadyParsedClasses = new HashSet<>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } for (String candidateName : newCandidateNames) { if (!oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry()); }if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); } }

processConfigBeanDefinitions()方法中,ConfigurationClassPostProcessor将解析Sprngboot启动类以得到ConfigurationClass的任务委托给了ConfigurationClassParser,将基于ConfigurationClass创建BeanDefinition并注册到注册表的任务委托给了ConfigurationClassBeanDefinitionReader,所以下面会对这两个步骤进行分析。首先是ConfigurationClassParser解析Springboot启动类,其parse()方法如下所示。
public void parse(Set configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition) { //Springboot启动类对应的BeanDefinition为AnnotatedGenericBeanDefinition //AnnotatedGenericBeanDefinition实现了AnnotatedBeanDefinition接口 parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } }//延迟处理DeferredImportSelector this.deferredImportSelectorHandler.process(); }

由于Springboot启动类对应的BeanDefinitionAnnotatedGenericBeanDefinition,而AnnotatedGenericBeanDefinition实现了AnnotatedBeanDefinition接口,所以继续看parse(AnnotationMetadata metadata, String beanName)方法,如下所示。
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER); }

继续看processConfigurationClass()方法,如下所示。
protected void processConfigurationClass(ConfigurationClass configClass, Predicate filter) throws IOException { if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; }ConfigurationClass existingClass = this.configurationClasses.get(configClass); if (existingClass != null) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } return; } else { this.configurationClasses.remove(configClass); this.knownSuperclasses.values().removeIf(configClass::equals); } }SourceClass sourceClass = asSourceClass(configClass, filter); do { //实际开始处理配置类 sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter); } while (sourceClass != null); this.configurationClasses.put(configClass, configClass); }

processConfigurationClass()方法中会调用doProcessConfigurationClass()方法来实际的处理配置类的@ComponentScan@Import@Bean等注解。在本节的论述中,其实一直是将Springboot启动类与其它配置类分开的,因为笔者认为Springboot启动类是一个特殊的配置类,其它配置类的扫描和加载均依赖Springboot启动类上的一系列注解(@ComponentScan@Import等)。上述processConfigurationClass()方法是一个会被递归调用的方法,第一次该方法被调用时,处理的配置类是Springboot的启动类,处理Springboot启动类时就会加载进来许多其它的配置类,那么这些配置类也会调用processConfigurationClass()方法来处理,因为其它配置类上可能也会有一些@Import@Bean等注解。这里只讨论第一次调用,即处理Springboot启动类的情况。doProcessConfigurationClass()方法源码如下所示。
@Nullable protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate filter) throws IOException {if (configClass.getMetadata().isAnnotated(Component.class.getName())) { processMemberClasses(configClass, sourceClass, filter); }//处理@PropertySource注解 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } }//处理@ComponentScan注解 Set componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { Set scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } }//处理@Import注解 processImports(configClass, sourceClass, getImports(sourceClass), filter, true); //处理@ImportResource注解 AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } }//处理由@Bean注解修饰的方法 Set beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); }processInterfaces(configClass, sourceClass); if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); return sourceClass.getSuperClass(); } }return null; }

doProcessConfigurationClass()方法中对于每种注解的处理会在后续文章中介绍,本文暂时不讨论。在processConfigurationClass()方法中处理完Springboot启动类之后,实际上此时只会将自定义bean(由@Component@Controller@Service等注解修饰的类)对应的ConfigurationClass,自定义配置类(由@Configuration注解修饰的类)对应的ConfigurationClass添加到ConfigurationClassParserconfigurationClasses中,那么最为关键的各种starter中的配置类对应的ConfigurationClass是在哪里添加的呢,回到ConfigurationClassParserparse()方法,下面再给出其源码,如下所示。
public void parse(Set configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition) { //这里处理完,ConfigurationClassParser的configurationClasses中只会有自定义bean和自定义配置类对应的ConfigurationClass parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } }//这里处理完,starter中的配置类对应的ConfigurationClass才会添加到ConfigurationClassParser的configurationClasses中 this.deferredImportSelectorHandler.process(); }

因为Springboot扫描starter并处理其配置类是依赖启动类上的@EnableAutoConfiguration注解,@EnableAutoConfiguration注解的功能由@Import(AutoConfigurationImportSelector.class)实现,其中AutoConfigurationImportSelector实现了DeferredImportSelector接口,而DeferredImportSelector表明需要被延迟处理,所以Springboot需要延迟处理AutoConfigurationImportSelector,延迟处理的地方就在上述parse()方法的最后一行代码,关于@Import注解,后续文章中会对其进行分析,这里暂时不讨论。现在定义一个TestBeanConfig配置类,在其中向容器注册TestBean,同时再定义一个由@Component注解修饰的TestComponent,代码如下所示。
@Configuration public class TestBeanConfig {@Bean public TestBean testBean() { return new TestBean(); }}

public class TestBean {public TestBean() { System.out.println("Initialize TestBean."); }}

@Component public class TestComponent {public TestComponent() { System.out.println("Initialize TestComponent."); }}

现在在ConfigurationClassParserparse()方法的this.deferredImportSelectorHandler.process(); 这一行代码打断点,程序运行到这里时,ConfigurationClassParser的configurationClasses如下所示。
Spring-@Configuration注解简析
文章图片

可见此时configurationClasses中没有starter中的配置类对应的ConfigurationClass,往下执行一行,此时ConfigurationClassParser的configurationClasses如下所示。
Spring-@Configuration注解简析
文章图片

可见此时starter中的配置类对应的ConfigurationClass已经被加载,至此ConfigurationClassParser解析Springboot启动类分析完毕。
【Spring-@Configuration注解简析】现在分析ConfigurationClassBeanDefinitionReader解析所有ConfigurationClass,并基于ConfigurationClass创建BeanDefinition并缓存到注册表的beanDefinitionMap中。首先是ConfigurationClassBeanDefinitionReaderloadBeanDefinitions()方法,如下所示。
public void loadBeanDefinitions(Set configurationModel) { TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator(); for (ConfigurationClass configClass : configurationModel) { loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } }

loadBeanDefinitions()方法中遍历每一个ConfigurationClass并调用了loadBeanDefinitionsForConfigurationClass()方法,继续看loadBeanDefinitionsForConfigurationClass()方法,如下所示。
private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {if (trackedConditionEvaluator.shouldSkip(configClass)) { String beanName = configClass.getBeanName(); if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { this.registry.removeBeanDefinition(beanName); } this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName()); return; }if (configClass.isImported()) { //基于ConfigurationClass自身创建BeanDefinition并缓存到注册表中 registerBeanDefinitionForImportedConfigurationClass(configClass); } for (BeanMethod beanMethod : configClass.getBeanMethods()) { //基于ConfigurationClass中由@Bean注解修饰的方法创建BeanDefinition并缓存到注册表中 loadBeanDefinitionsForBeanMethod(beanMethod); }loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }

上述loadBeanDefinitionsForConfigurationClass()方法中,除了将自身创建为BeanDefinition外,还会将所有由@Bean注解修饰的方法(如果有的话)创建为BeanDefinition,所有创建的BeanDefinition最后都会注册到注册表中,即缓存到DefaultListableBeanFactory的beanDefinitionMap中。至此,ConfigurationClassBeanDefinitionReader解析所有ConfigurationClass的大致流程也分析完毕。
总结 由@Configuration注解修饰的配置类结合@Bean注解可以实现向容器注册bean的功能,同时也可以借助@ComponentScan@Import等注解将其它配置类扫描到容器中。Springboot的启动类就是一个配置类,通过ConfigurationClassPostProcessor处理Springboot启动类,可以实现将自定义的bean,自定义的配置类和各种starter中的配置类扫描到容器中,以达到自动装配的效果。

    推荐阅读