前言

Spring中的@Configuration注解润饰的类被称为配置类,通过配置类能够向容器注册bean以及导入其它配置类,本篇文章将联合例子和源码对@Configuration注解原理进行学习,并引出对Spring框架在解决配置类过程中起重要作用的ConfigurationClassPostProcessor的探讨。

Springboot版本:2.4.1

注释

一. @Configuration注解简析

基于@Configuration注解能够实现基于JavaConfig的形式来申明Spring中的bean,与之作为比照的是基于XML的形式来申明bean。由@Configuration注解标注的类中所有由@Bean注解润饰的办法返回的对象均会被注册为Spring容器中的bean,应用举例如下。

@Configurationpublic 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@Configurationpublic @interface SpringBootConfiguration {    ......}

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

此时Springboot容器持有的DefaultListableBeanFactory中的beanDefinitionMap中曾经存在了Springboot启动类对应的BeanDefinition,在初始化Springboot容器阶段,Springboot启动类对应的BeanDefinition会首先被解决,通过解决Springboot启动类对应的BeanDefinition才会引入对其它配置类的解决。对于Springboot启动类,临时理解到这里,上面再给出一张解决配置类的调用链,以供后续浏览参考。

本篇文章后续将从ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry()办法开始,对由@Configuration注解润饰的配置类的解决进行阐明。

二. ConfigurationClassPostProcessor解决配置类

通过第一节中的调用链可知,在Springboot启动时,初始化容器阶段会调用到ConfigurationClassPostProcessor来解决配置类,即由@Configuration注解润饰的类。ConfigurationClassPostProcessor是由Spring框架提供的bean工厂后置处理器,类图如下所示。

可知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注册到了DefaultListableBeanFactorybeanDefinitionMap中,所以注册表中的配置类指的就是Springboot的启动类(前文已知Springboot的启动类就是一个配置类),而派生进一步的bean定义,就是将Springboot启动类上@EnableAutoConfiguration@ComponentScan等注解加载的配置类解析为BeanDefinition并注册到DefaultListableBeanFactorybeanDefinitionMap中。临时不分明在Springboot启动流程中,ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry()办法正文中提到的配置类是否会有除了Springboot启动类之外的配置类,欢送留言探讨。

即当初晓得,ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry()办法次要解决指标就是Springboot的启动类,通过解决Springboot启动类引出对其它配置类的解决,上面追随源码,进行学习。postProcessBeanDefinitionRegistry()办法如下所示。

@Overridepublic 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,最初缓存在ConfigurationClassParserconfigurationClasses中;
  • 创立ConfigurationClassBeanDefinitionReader,解析所有ConfigurationClass,基于ConfigurationClass创立BeanDefinition并缓存到注册表的beanDefinitionMap中。

processConfigBeanDefinitions()办法源码如下。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {    List<BeanDefinitionHolder> 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<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);    Set<ConfigurationClass> 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<ConfigurationClass> 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<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));            Set<String> 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<BeanDefinitionHolder> 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<String> 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()办法源码如下所示。

@Nullableprotected final SourceClass doProcessConfigurationClass(        ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> 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<AnnotationAttributes> 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<BeanDefinitionHolder> 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<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");        for (String resource : resources) {            String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);            configClass.addImportedResource(resolvedResource, readerClass);        }    }    //解决由@Bean注解润饰的办法    Set<MethodMetadata> 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<BeanDefinitionHolder> 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,代码如下所示。

@Configurationpublic class TestBeanConfig {    @Bean    public TestBean testBean() {        return new TestBean();    }}
public class TestBean {    public TestBean() {        System.out.println("Initialize TestBean.");    }}
@Componentpublic class TestComponent {    public TestComponent() {        System.out.println("Initialize TestComponent.");    }}

当初在ConfigurationClassParserparse()办法的this.deferredImportSelectorHandler.process();这一行代码打断点,程序运行到这里时,ConfigurationClassParserconfigurationClasses如下所示。

可见此时configurationClasses中没有starter中的配置类对应的ConfigurationClass,往下执行一行,此时ConfigurationClassParserconfigurationClasses如下所示。

可见此时starter中的配置类对应的ConfigurationClass曾经被加载,至此ConfigurationClassParser解析Springboot启动类剖析结束。

当初剖析ConfigurationClassBeanDefinitionReader解析所有ConfigurationClass,并基于ConfigurationClass创立BeanDefinition并缓存到注册表的beanDefinitionMap中。首先是ConfigurationClassBeanDefinitionReaderloadBeanDefinitions()办法,如下所示。

public void loadBeanDefinitions(Set<ConfigurationClass> 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最初都会注册到注册表中,即缓存到DefaultListableBeanFactorybeanDefinitionMap中。至此,ConfigurationClassBeanDefinitionReader解析所有ConfigurationClass的大抵流程也剖析结束。

总结

@Configuration注解润饰的配置类联合@Bean注解能够实现向容器注册bean的性能,同时也能够借助@ComponentScan@Import等注解将其它配置类扫描到容器中。Springboot的启动类就是一个配置类,通过ConfigurationClassPostProcessor解决Springboot启动类,能够实现将自定义的bean,自定义的配置类和各种starter中的配置类扫描到容器中,以达到主动拆卸的成果。