揭开Spring Boot自动配置的神秘面纱

2022-12-01 21:42:22 浏览数 (1)

Running with Spring Boot v2.5.7

Spring自2003年发布至今已有近20年的历史,在发布后的短短几年便已成为开发Java应用程序的事实标准,影响着无数国内外Java开发人员。尽管如此,Spring团队并没有放慢自己的脚步,于2014年,发布了Spring Boot。Spring Boot能用寥寥数行代码构建出一套Hello World应用程序,不费吹灰之力!这主要得益于它的两大重量级特性:起步依赖(starter dependency) 和自动配置(auto configuration);此外,Spring Boot还自带了metrics、health checks、embeded servlet container 和 externalized configuration等特性,这些都让选择Spring Boot成为了一件顺理成章的事情。可以说,Spring Boot正是Java社区十几年来所探寻的那种能够让Java开发变得趣味横生的框架。

虽然起步依赖不是本文的重点,但起步依赖和自动配置是一种相辅相成的关系,所以有必要简单介绍一下起步依赖的基本概念。在Spring Boot还未问世前,向Spring应用中添加依赖是一件富有挑战性的工作,主要体现在:1) 需要哪些依赖;2) 依赖组件的版本信息是否与当前应用中的其他依赖相匹配。起步依赖就是为了解决这俩痛点问题的,Spring Boot预置了若干不同功能的starter组件,每个starter组件通过依赖传递可以将需要的外部依赖聚合起来,而且这些外部依赖的版本经过官方严格测试,不会导致依赖冲突。举个例子,如果大家需要基于Spring Boot开发Java Web应用,只需要在项目中引入spring-boot-starter-web这一个依赖即可,它会自动将tomcat、jackson、spring-web和spring-webmvc等依赖聚合到当前应用中,如下图所示:

1 自动配置的概念

对于第三方starter组件而言,自动配置相关内容一般存放在各自autoconfigure模块中,当然也有直接封装在starter模块内的;不同于第三方starter组件,Spring Boot内置的starter组件本身并不会包含自动配置类,而是统一将自动配置类存放于spring-boot-autoconfigure模块中,在该模块classpath:META-INF目录下的spring.factories文件中存在100多个以AutoConfiguration为后缀的自动配置类,如下:

还记得RestTemplate吗?它是Spring为开发人员提供的一款访问外部RESTful API的利器,其针对每个HTTP方法进行了更高级别的抽象。在Spring Boot应用中使用RestTemplate极其简单,只需要像下面这样声明一个配置类,然后通过@Resource@Autowired注解将RestTemplate实例注入到业务类中即可。

代码语言:javascript复制
@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
        restTemplateBuilder.setConnectTimeout(Duration.ofSeconds(15))
                .setReadTimeout(Duration.ofSeconds(15));
        return restTemplateBuilder.build();
    }
}

不知道大家有没有问过自己,这个RestTemplateBuilder类型的Bean是从哪里蹦出来的?答案就是自动配置。一起来看一下spring-boot-autoconfigure模块内RestTemplateAutoConfiguration中的内容,如下所示:

代码语言:javascript复制
package org.springframework.boot.autoconfigure.web.client;

@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(HttpMessageConvertersAutoConfiguration.class)
@ConditionalOnClass(RestTemplate.class)
@Conditional(NotReactiveWebApplicationCondition.class)
public class RestTemplateAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public RestTemplateBuilderConfigurer restTemplateBuilderConfigurer(
            ObjectProvider<HttpMessageConverters> messageConverters,
            ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers,
            ObjectProvider<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) {
        RestTemplateBuilderConfigurer configurer = new RestTemplateBuilderConfigurer();
        configurer.setHttpMessageConverters(messageConverters.getIfUnique());
        configurer.setRestTemplateCustomizers(restTemplateCustomizers.orderedStream().collect(Collectors.toList()));
        configurer.setRestTemplateRequestCustomizers(restTemplateRequestCustomizers.orderedStream().collect(Collectors.toList()));
        return configurer;
    }

    @Bean
    @ConditionalOnMissingBean
    public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
        RestTemplateBuilder builder = new RestTemplateBuilder();
        return restTemplateBuilderConfigurer.configure(builder);
    }
}

上述源码交代了三件事:

  1. 针对RestTemplate自动配置的触发时机要在HttpMessageConvertersAutoConfiguration之后。
  2. 针对RestTemplate自动配置的触发条件有两个:一是当前classpath下必须要有RestTemplate.class文件;二是当前应用必须基于Servlet API构建,说人话就是只能存在spring-webmvc依赖,不能含有spring-webflux依赖。
  3. 声明了RestTemplateBuilderConfigurerRestTemplateBuilder这两个Bean,而且还特意标注了@ConditionalOnMissingBean注解,真是体贴周到啊!在该注解的加持下,我们就可以重新声明这俩Bean,而弃用预置的,这往往很有必要。如果有人疑惑:为什么官方的自动配置模块没有一步到位,直接声明一个RestRemplate类型的Bean呢?天空飞来十个字:授人以鱼不如授人以渔。

一句话,自动配置指的是在若干条件下为Spring Boot应用自动声明一个或多个开箱即用的、具备某一功能的Bean!

2 自动配置的实现原理

众所周知,一个Bean在达到可用条件前,往往需要经历实例化属性填充初始化这三个阶段,通过自动配置所声明的Bean也不例外。而BeanDefinition封装了这三个阶段的具体行为逻辑,显然,BeanDefinition是触发Bean生命周期的前提,因此要想搞明白自动配置的实现原理,我们首先要清楚与自动配置Bean相关联的BeanDefinition是从何而来的;至于Bean的实例化、属性填充和初始化,这些内容应该被归纳到Bean的生命周期这一话题中更为合理。

在主角登场前,先简单介绍下Spring中的两位大哥,分别是:BeanFactoryPostProcessorBeanDefinitionRegistryPostProcessor,后者继承自前者。BeanFactoryPostProcessor

代码语言:javascript复制
public interface BeanFactoryPostProcessor {
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}

BeanFactoryPostProcessorBeanPostProcessor均是Spring中常用的IoC拓展点,与BeanPostProcessor不同的是,BeanFactoryPostProcessor只能与BeanDefinition交互,而非与Bean实例交互,比如,我们可以通过BeanFactoryPostProcessor将某一BeanDefinition实例的beanClass属性值替换为CGLIB所生成的Class实例,这样后期通过该BeanDefinition实例所生成的Bean对象就得到增强了。BeanDefinitionRegistryPostProcessor

代码语言:javascript复制
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry);
}

BeanDefinitionRegistryPostProcessor是对BeanFactoryPostProcessor的进一步拓展,主要体现在其不仅继承了BeanFactoryPostProcessor的能力,还独自引入了一个postProcessBeanDefinitionRegistry(registry)方法,该方法的入参类型为BeanDefinitionRegistry,那肯定奔着BeanDefinitionRegistry的registerBeanDefinition(beanName, beanDefinition)方法去的,也就是说BeanDefinitionRegistryPostProcessor具备向BeanDefinitionRegistry注册BeanDefinition的能力。

回归正题!在官方的spring-boot-autoconfigure模块亦或第三方的xxxx-spring-boot-autoconfigure中,凡是以AutoConfiguration为后缀的自动配置类都会由@Configuration标注,而负责引导处理@Configuration注解的正是ConfigurationClassPostProcessor,没错,主角大佬来了!

从上述继承关系图来看,ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor接口,而后者又继承了BeanFactoryPostProcessor接口,关于这俩接口很熟悉对不对?ConfigurationClassPostProcessor会通过BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry(registry)方法向BeanDefinitionRegistry注册BeanDefinition,然后通过BeanFactoryPostProcessor的postProcessBeanFactory(beanFactory)方法与BeanDefinition实例交互。主干内容如下:

代码语言:javascript复制
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {
    /**
     * Derive further bean definitions from the configuration classes in the registry.
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        processConfigBeanDefinitions(registry);
    }

    /**
     * Prepare the Configuration classes for servicing bean requests at runtime
     * by replacing them with CGLIB-enhanced subclasses.
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        enhanceConfigurationClasses(beanFactory);
        beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
    }
}

下面分小节对这俩核心方法进行解读。

2.1 postProcessBeanDefinitionRegistry(registry)

代码语言:javascript复制
public class ConfigurationClassPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        processConfigBeanDefinitions(registry);
    }
}

接下来,进入processConfigBeanDefinitions()方法一探究竟。一句卧槽走天下的杜小头看了源码内容之后,还能说啥呢?于是在对源码进行删减的基础上,将整个内容划分为三个部分,具体如下:

代码语言:javascript复制
public class ConfigurationClassPostProcessor {
    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
        String[] candidateNames = registry.getBeanDefinitionNames();
        /*
         * Part 1
         */
        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }

        // Return immediately if no @Configuration classes were found
        if (configCandidates.isEmpty()) {
            return;
        }

        // Sort by previously determined @Order value, if applicable
        configCandidates.sort((bd1, bd2) -> {
            int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
            int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
            return Integer.compare(i1, i2);
        });

        // Parse each @Configuration class
        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 {
            /*
             * Part 2
             */ 
            parser.parse(candidates);

            Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
            configClasses.removeAll(alreadyParsed);

            // Read the model and create bean definitions based on its content
            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());
            }
            /*
             * Part 3
             */    
            this.reader.loadBeanDefinitions(configClasses);
            alreadyParsed.addAll(configClasses);

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

Part 1 | configCandidates.add(new BeanDefinitionHolder(beanDef, beanName))

Part 1 主要围绕一个for循环展开:

代码语言:javascript复制
for (String beanName : candidateNames) {
     BeanDefinition beanDef = registry.getBeanDefinition(beanName);
     if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
          configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
     }
}

显然,当前for循环用于从BeanDefinitionRegistry中的BeanDefinition实例中筛选出配置类,一经发现,立即将候选者放入List类型的configCandidates变量中。what?难道这个时候BeanDefinition注册表中就已经持有所有的BeanDefinition实例了?别慌!大概也就数十个优先级比较高的BeanDefinition实例而已。换句话说,在执行postProcessBeanDefinitionRegistry(registry)方法前,BeanDefinitionRegistry中已经驻留若干BeanDefinition实例了;在这若干BeanDefinition实例中,绝大多数扮演着ROLE_INFRASTRUCTURE的角色,即基础设施,用于保障Spring框架相关功能正常流转的(答应我,千万别读完本文后只记得这个单词了,好吗);抛开这些基础设施,还有一个普通角色的BeanDefinition实例值得我们关注,如下图所示:

关于配置类的筛选条件是一个重点内容,内容如下:

代码语言:javascript复制
public class ConfigurationClassUtils {
    public static final String CONFIGURATION_CLASS_ATTRIBUTE = Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");

    private static final Set<String> candidateIndicators = new HashSet<>(8);
    static {
        candidateIndicators.add(Component.class.getName());
        candidateIndicators.add(ComponentScan.class.getName());
        candidateIndicators.add(Import.class.getName());
        candidateIndicators.add(ImportResource.class.getName());
    }

    /**
     * Check whether the given bean definition is a candidate for a configuration class
     */
    public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
        String className = beanDef.getBeanClassName();
        if (className == null || beanDef.getFactoryMethodName() != null) {
            return false;
        }

        AnnotationMetadata metadata;
        if (beanDef instanceof AnnotatedBeanDefinition && className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
            metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
        } else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
            Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
            if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
                    BeanPostProcessor.class.isAssignableFrom(beanClass) ||
                    AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
                    EventListenerFactory.class.isAssignableFrom(beanClass)) {
                return false;
            }
            metadata = AnnotationMetadata.introspect(beanClass);
        }

        Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
        if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "full");
        } else if (config != null || isConfigurationCandidate(metadata)) {
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "lite");
        } else {
            return false;
        }
        return true;
    }

    /**
     * Check the given metadata for a configuration class candidate
     */
    public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
        // Do not consider an interface or an annotation...
        if (metadata.isInterface()) {
            return false;
        }
        // Any of the typical annotations found?
        for (String indicator : candidateIndicators) {
            if (metadata.isAnnotated(indicator)) {
                return true;
            }
        }
        // Finally, let's look for @Bean methods...
        return hasBeanMethods(metadata);
    }
}

筛选逻辑挺严谨,一大坨!但耐心读完后,应该很容易得出以下结论:

  1. 如果某一BeanDefinition所表征的Bean属于BeanFactoryPostProcessorBeanPostProcessorAopInfrastructureBeanEventListenerFactory这四种类型之一,则不属于配置类;
  2. 如果某一BeanDefinition所表征的Bean直接或间接地由@Configuration@Component@ComponentScan@Import@ImportResource这五种注解中的一种标注或者含有由@Bean标注的方法,那就属于配置类;
  3. 如果某一BeanDefinition所表征的Bean属于配置类而且由@Configuration(proxyBeanMethods = true)标注,那么该BeanDefinition的CONFIGURATION_CLASS_ATTRIBUTE属性值就为full,否则为lite

Part 2 | parser.parse(candidates)

Part 2 中的内容是全文核心所在,主要围绕ConfigurationClassParser展开,主要内容为:

代码语言:javascript复制
public class ConfigurationClassParser {
    public void parse(Set<BeanDefinitionHolder> configCandidates) {
        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            ConfigurationClass configurationClass = null;
            if (bd instanceof AnnotatedBeanDefinition) {
                configurationClass = new ConfigurationClass(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
            } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                configurationClass = new ConfigurationClass(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
            } else {
                MetadataReader reader = this.metadataReaderFactory.getMetadataReader(bd.getBeanClassName());
                configurationClass = new ConfigurationClass(reader, beanName);
            }
            // 核心内容
            processConfigurationClass(configurationClass);
        }
        // 核心内容
        deferredImportSelectorHandler.process();
    }
}

显然,核心内容最终由processConfigurationClass()deferredImportSelectorHandler.process()这两个方法承载,咱们一个一个来分析。

I. processConfigurationClass()

代码语言:javascript复制
public class ConfigurationClassParser {
    protected void processConfigurationClass(ConfigurationClass configClass) {
        SourceClass sourceClass = asSourceClass(configClass, filter);
        do {
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        } while (sourceClass != null);
        this.configurationClasses.put(configClass, configClass);
    }
}

ConfigurationClassprocessConfigurationClass()方法的入参类型,它是对配置类的封装,它持有若干成员变量,如:beanName、与方法级注解@Bean相关的beanMethods、与类级注解@ImportResource相关的importedResources、与类级注解@Import(ImportBeanDefinitionRegistrar.class)相关的importBeanDefinitionRegistrars和表征该配置类由谁引入的importedBy等。

从上述源码来看,每执行一次processConfigurationClass()方法,最终都会往ConfigurationClassParser中的成员变量configurationClasses中插入一个ConfigurationClass实例,这很重要!但doProcessConfigurationClass()方法同样别有洞天。对于每一个配置类,它们都会先被转换为ConfigurationClass实例,然后经doProcessConfigurationClass()方法处理。在doProcessConfigurationClass()方法中,会依次处理与当前配置类相关的nested member class@PropertySource@ComponentScan@Import@ImportResource@Bean等内容。

对于nested member class,首先从当前ConfigurationClass实例中探测出嵌套成员类;然后通过ConfigurationClassUtils判断嵌套成员类是否是配置类,若是配置类,则再次将嵌套成员配置类交由processConfigurationClass()方法处理。

对于@PropertySource注解,首先从当前ConfigurationClass实例中拿到@PropertySource注解的元数据;然后在ConfigurableEnvironment中插入PropertySource实例。

对于@ComponentScan注解,首先从当前ConfigurationClass实例中拿到@ComponentScan注解的元数据;然后委派ComponentScanAnnotationParser进行扫描;紧接着,ComponentScanAnnotationParser会根据@ComponentScan注解中basePackagesbasePackageClasses这俩属性来探测出所要扫描的包名,如果没有获取到,就根据@ComponentScan注解所依附宿主类的包名为扫描目标;最后将目标包下所有由@Component注解标注的类扫描出来并封装为BeanDefinition实例,逐个将BeanDefinition实例注册到BeanDefinitionRegistry中。在默认情况下,由于Spring Boot的启动类由@ComponentScan注解来标注,因此Spring Boot启动类所在包就是最终的扫描目标,那么Spring Boot工程classpath下所有与该包匹配的通过@Component注解声明的类将会被优先扫描到并注册到BeanDefinition注册表中,这是一个重要知识点,同时这也正是为什么启动类总处于顶级包名下的答案!顺便提一句,@Controller@RestController@Service@Repository@Configuration等注解都是由@Component标注的哈!核心内容如下:

代码语言:javascript复制
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
     String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
     Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
     basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
     basePackages.add(ClassUtils.getPackageName(declaringClass));
}
Set<BeanDefinitionHolder> BeanDefinitionHolderSet = scanner.doScan(StringUtils.toStringArray(basePackages));

对于@Import注解,由于其只能引入ImportSelectorImportBeanDefinitionRegistrar@Configuration这三种类型,那自然是针对它们进行处理。processImports()方法即用于处理@Import注解,内容如下:

代码语言:javascript复制
public class ConfigurationClassParser {
    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates) {
        if (importCandidates.isEmpty()) {
            return;
        }
        for (SourceClass candidate : importCandidates) {
            if (candidate.isAssignable(ImportSelector.class)) {
                Class<?> candidateClass = candidate.loadClass();
                ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry);
                if (selector instanceof DeferredImportSelector) {
                    this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                } else {
                    String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                    Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                    // 递归
                    processImports(configClass, currentSourceClass, importSourceClasses);
                }
            } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                Class<?> candidateClass = candidate.loadClass();
                ImportBeanDefinitionRegistrar registrar = ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);
                configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
            } else {
                this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
            }
        }
    }
}

从上述源码内容来看,针对由@Import注解标注的配置类,则根据所引入对象的类型分为四个分支:1) 如果引入的是ImportSelector,则递归调用processImports()方法;2) 如果引入的是DeferredImportSelector,则调用DeferredImportSelectorHandler中的handle()方法,一般就是构造一个DeferredImportSelectorHolder而已;3) 如果引入的是ImportBeanDefinitionRegistrar,则填充ConfigurationClass实例中的importBeanDefinitionRegistrars属性;4) 如果引入的是普通@Configuration配置类,则继续委派processConfigurationClass()方法进行处理。也许大家已经察觉到了:只有当@Import注解引入的是普通@Configuration配置类和ImportSelector实现类时,processImports()方法才能往成员变量configurationClasses中插入一个ConfigurationClass实例。

在很多配置类和组合注解中都有@Import注解的身影,比如组合注解@EnableAutoConfiguration,而@EnableAutoConfiguration正是开启Spring Boot自动配置特性的开关,如下:

代码语言:javascript复制
-------------------------
@EnableAutoConfiguration
-------------------------
    < @AutoConfigurationPackage 
        < @Import(AutoConfigurationPackages.Registrar.class)
-------------------------
    < @Import(AutoConfigurationImportSelector.class)

在Spring Boot中,启动类也会由@EnableAutoConfiguration标注,那么当processConfigurationClass()方法处理启动类这一配置类时,就会以AutoConfigurationImportSelector为突破口,将自动配置类解析出来?大错特错!因为AutoConfigurationImportSelector属于DeferredImportSelector系,它是一种延迟解析类型的ImportSelector。

继续!对于@ImportResource注解,首先从当前ConfigurationClass实例中拿到@ImportResource注解的元数据;然后将ImportResource实例填充到当前ConfigurationClass实例的成员变量importedResources中。

对于@Bean注解,主要是将BeanMethod回填到当前ConfigurationClass实例中的beanMethods属性中;

最后,根据当前ConfigurationClass获取其所实现的接口,只要父接口中含有@Bean方法,那就再次将BeanMethod回填到当前ConfigurationClass实例中的beanMethods属性中。关于这一点,笔者也是看到这里才知道的,如下所示,InterfaceConfig接口中声明了一个由@Bean注解标注的默认方法(Java 8引入的一个新特性)。

代码语言:javascript复制
public interface InterfaceConfig {
    @Bean
    default Demo demo() {
        return new Demo();
    }
}
@Configuration
public AppConfig implements InterfaceConfig {

}

II. deferredImportSelectorHandler.process()

deferredImportSelectorHandler.process() 用于将所有自动配置模块classpath:META-INF目录下spring.factories文件中的自动配置类解析出来,然后将其追加到ConfigurationClassParser中的configurationClasses这一成员变量中。所谓所有自动配置模块,包括官方 spring-boot-autoconfigure 和第三方 xxoo-spring-boot-autoconfigure/starter 模块。

插播一条广告!在Spring Boot启动初期,就已经通过getSpringFactoryNames()方法提前将各模块下classpath:META-INF/spring.factories文件中的内容拿到了,并缓存起来。具体逻辑落在SpringFactoriesLoader中,如下:

代码语言:javascript复制
public final class SpringFactoriesLoader {
    static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();

    public static <T> List<T> loadFactories(Class<T> factoryType, ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        List<String> factoryImplementationNames = loadSpringFactories(factoryType, classLoaderToUse);
        List<T> result = new ArrayList<>(factoryImplementationNames.size());
        for (String factoryImplementationName : factoryImplementationNames) {
            result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
        }
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }

    private static List<String> loadSpringFactories(Class<?> factoryType, ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        Map<String, List<String>> result = cache.get(classLoaderToUse);
        if (result != null) {
            return result;
        }
        result = new HashMap<>();
        try {
            Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    String[] factoryImplementationNames =
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                    for (String factoryImplementationName : factoryImplementationNames) {
                        result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()).add(factoryImplementationName.trim());
                    }
                }
            }
            result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                    .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
            cache.put(classLoader, result);
        } catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from META-INF/spring.factories", ex);
        }
        return result.getOrDefault(factoryTypeName, Collections.emptyList());
    }
}

继续!在ConfigurationClassParser中若干嵌套内部类,其中DeferredImportSelectorHandler就是其中之一,process()方法内容如下:

代码语言:javascript复制
public class ConfigurationClassParser {
    private class DeferredImportSelectorHandler {
        public void process() {
            List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
            this.deferredImportSelectors = null;
            try {
                if (deferredImports != null) {
                    DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
                    deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
                    deferredImports.forEach(handler::register);
                    handler.processGroupImports();
                }
            } finally {
                this.deferredImportSelectors = new ArrayList<>();
            }
        }
    }
}

接着,进入DeferredImportSelectorGroupingHandlerprocessGroupImports()方法中,如下:

代码语言:javascript复制
public class ConfigurationClassParser {
    private class DeferredImportSelectorGroupingHandler {
        public void processGroupImports() {
            for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
                Predicate<String> exclusionFilter = grouping.getCandidateFilter();
                grouping.getImports().forEach(entry -> {
                    ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
                    processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter), Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)));
                });
            }
        }
    }
}

从上述内容来看,最终依然是委派processImports()方法对ConfigurationClass进行处理,但这里应该重点关注grouping.getImports()中的内容。

接着,进入DeferredImportSelectorGroupinggetImports()方法中,如下:

代码语言:javascript复制
public class ConfigurationClassParser {
    private static class DeferredImportSelectorGrouping {
        private final DeferredImportSelector.Group group;
        private final List<DeferredImportSelectorHolder> deferredImports = new ArrayList<>();
        DeferredImportSelectorGrouping(DeferredImportSelector.Group group) {
            this.group = group;
        }

        public void add(DeferredImportSelectorHolder deferredImport) {
            this.deferredImports.add(deferredImport);
        }
        
        public Iterable<DeferredImportSelector.Group.Entry> getImports() {
            for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
                this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                        deferredImport.getImportSelector());
            }
            return this.group.selectImports();
        }
    }
}

从上述内容来看,DeferredImportSelector.Group接口中的process()方法和selectImports()方法可能就是最终的处理逻辑了,内容如下:

代码语言:javascript复制
public class AutoConfigurationImportSelector implements DeferredImportSelector {
    private static class AutoConfigurationGroup implements DeferredImportSelector.Group {
        private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
        private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();

        @Override
        public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
            List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
            configurations = removeDuplicates(configurations);
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = getConfigurationClassFilter().filter(configurations);
            AutoConfigurationEntry autoConfigurationEntry = new AutoConfigurationEntry(configurations, exclusions);
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }
        }

        @Override
        public Iterable<Entry> selectImports() {
            if (this.autoConfigurationEntries.isEmpty()) {
                return Collections.emptyList();
            }
            Set<String> allExclusions = this.autoConfigurationEntries.stream()
                    .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
            Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
                    .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
                    .collect(Collectors.toCollection(LinkedHashSet::new));
            processedConfigurations.removeAll(allExclusions);
            return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
                    .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
                    .collect(Collectors.toList());
        }
    }
}

process()方法中,首先从SpringFactoriesLoader的缓存变量中以EnableAutoConfiguration作为key取出所有自动配置类;然后再从SpringFactoriesLoader中缓存变量中以AutoConfigurationImportFilter作为key取出所有自动配置过滤器,主要是OnBeanConditionOnClassConditionOnWebApplicationCondition,借助过滤器过滤掉无效的自动配置类;最后,将存活下来的自动配置类填充到AutoConfigurationEntry列表中。在selectImports()方法中并无啥重要内容,主要是将AutoConfigurationEntry列表中的有效自动配置类转换为Iterable<Entry>,最后遍历该Iterable,依次交由processImports()方法处理,processImports()方法在 Part 2 部分介绍 processConfigurationClass()方法的时候已经介绍过了!

Part 3 | this.reader.loadBeanDefinitions(configClasses)

Part 2 执行完毕后,最终的自动配置类都已经解析出来并保存在ConfigurationClassParser中的configurationClasses这一成员变量中了,但还没有将其注册到BeanDefinitionRegistry中。Part 3 主要围绕ConfigurationClassBeanDefinitionReader中的loadBeanDefinitions()方法展开,如下:

代码语言:javascript复制
public class ConfigurationClassBeanDefinitionReader {
    public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
        TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
        for (ConfigurationClass configClass : configurationModel) {
            loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
        }
    }
}

具体内容也就不展开了,主要是逐个遍历ConfigurationClass实例,每个ConfigurationClass实例中的属性在 Part 2 阶段都已填充完毕,下面要做的无非是将ConfigurationClass实例中所封装的配置类注册到BeanDefinitionRegistry中罢了。

2.2 postProcessBeanFactory(beanFactory)

代码语言:javascript复制
public class ConfigurationClassPostProcessor {
    /**
     * Prepare the Configuration classes for servicing bean requests at runtime
     * by replacing them with CGLIB-enhanced subclasses.
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        enhanceConfigurationClasses(beanFactory);
        beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
    }
}

接下来,进入enhanceConfigurationClasses()方法一探究竟,注意:此时beanFactory中已经持有所有的BeanDefinition实例了。第一个for循环主要用于筛选出哪些BeanDefinition所对应的Bean需要后期为其进行CGLIB增强,如果某一BeanDefinition的ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE属性值为full,那就将其填充进configBeanDefs变量中;第二个for循环用于遍历configBeanDefs变量,为每一个BeanDefinition生成全新的CGLIB增强类同时替换beanClass属性值,这样后期在通过该BeanDefinition实例化Bean的时候,Bean就是一个增强型代理类了。换句话说,IoC容器中驻留的就不是本尊了,而是CGLIB代理类。相关逻辑如下:

代码语言:javascript复制
public class ConfigurationClassPostProcessor {
    public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
        Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
            Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
            if ("full".equals(configClassAttr)) {
                configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
            }
        }
        if (configBeanDefs.isEmpty()) {
            // nothing to enhance -> return immediately
            return;
        }

        ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
        for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
            AbstractBeanDefinition beanDef = entry.getValue();
            // If a @Configuration class gets proxied, always proxy the target class
            beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
            // Set enhanced subclass of the user-specified bean class
            Class<?> configClass = beanDef.getBeanClass();
            Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
            if (configClass != enhancedClass) {
                beanDef.setBeanClass(enhancedClass);
            }
        }
    }
}

值得一提的是:这里通过CGLIB所生成的代理类都会自动实现EnhancedConfiguration接口,该接口是一个marker interface,即一种标识性的空接口。之所以能够自动实现该接口,是因为在构建Enhancer实例的时候, 通过setInterfaces()方法显式设置的,如下所示:

代码语言:javascript复制
public class ConfigurationClassEnhancer {
    public Class<?> enhance(Class<?> configClass, ClassLoader classLoader) {
        if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
            return configClass;
        }
        Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
        return enhancedClass;
    }

    private Enhancer newEnhancer(Class<?> configSuperClass, ClassLoader classLoader) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(configSuperClass);
        enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
        enhancer.setUseFactory(false);
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
        enhancer.setCallbackFilter(CALLBACK_FILTER);
        enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
        return enhancer;
    }
}

前文提到:只有在由@Configuration(proxyBeanMethods = true)直接修饰的配置类所对应的BeanDefinition实例中,ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE的值才为full,即最终会每个@Configuration(proxyBeanMethods = true)配置类生成CGLIB代理,为什么呢?上图!

仔细观察上图,operatorService()方法和logRecordPersistenceService()方法均由@Bean标注,那么这两个方法会分别通过OperatorService和LogRecordPersistenceService的构造方法来实例化Bean;可是在operationLogPointcutAdvisor()方法中,这两方法又被调用一次,难道构造方法又要执行一次?如果是的话,那单例Bean不就是笑话吗?事实上,OperatorService和LogRecordPersistenceService的构造方法只会被执行一次,因为OperationLogAutoConfigurationImportSelector会被CGLIB增强,即该Bean在IoC容器中是一个CGLIB代理类。注意一点,@Configuration注解接口中有一个属性:boolean proxyBeanMethods() default true,即默认为@Configuration配置类生成CGLIB代理。事实上,还是尽量不要生成CGLIB代理吧,因为这会延长Spring Boot工程的启动时间。

enhanceConfigurationClasses()方法执行完毕之后,紧接着向BeanFactory中插入了一个ImportAwareBeanPostProcessor实例,这是一个BeanPostProcessor实现类,核心内容如下:

代码语言:javascript复制
private static class ImportAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    private final BeanFactory beanFactory;

    public ImportAwareBeanPostProcessor(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if (bean instanceof ImportAware) {
            ImportRegistry importRegistry = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class);
            AnnotationMetadata importingClass = importRegistry.getImportingClassFor(ClassUtils.getUserClass(bean).getName());
            if (importingClass != null) {
                ((ImportAware) bean).setImportMetadata(importingClass);
            }
        }
        return bean;
    }
}

从上述源码来看,ImportAwareBeanPostProcessor主要服务于ImportAware类型的Bean。具体地,在对ImportAware类型的Bean执行初始化逻辑之前,通过ImportAware的setImportMetadata()方法为该Bean透传AnnotationMetadata实例对象。一脸懵逼?ImportAware类型的Bean一般要满足两个条件:1) 实现ImportAware接口;2) 由@Configuration注解接口标记。ProxyTransactionManagementConfiguration就是一个标准ImportAware类型的Bean,它的父类AbstractTransactionManagementConfiguration实现了ImportAware接口,详情如下:

代码语言:javascript复制
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
    // ignore contents
}

那为什么ImportAware类型的Bean需要显式地为其透传AnnotationMetadata实例对象呢?回忆一下,Import注解接口既可以引入ImportSelector也可以引入ImportBeanDefinitionRegistrar,还可以引入@Configuration;但前两者均持有参数类型为AnnotationMetadata的方法,这样就能够接收传进来的AnnotationMetadata实例对象;可由@Configuration修饰的配置类要想拥有接收AnnotationMetadata实例对象的能力,只能实现ImportAware接口。

3 总结

应该说,Spring Boot自动配置这一特性是对Spring 4中@Conditional注解的完美落地,但关于这方面知识本文并未涉及,但并不代表不重要。

参考文档

  1. https://docs.spring.io/spring-boot/docs/2.5.7/reference/htmlsingle/
  2. Spring Boot in Action | Craig Walls

0 人点赞