前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >揭开Spring Boot自动配置的神秘面纱

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

作者头像
程序猿杜小头
发布2022-12-01 21:42:22
7710
发布2022-12-01 21:42:22
举报
文章被收录于专栏:程序猿杜小头程序猿杜小头

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
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-02-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序猿杜小头 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 自动配置的概念
  • 2 自动配置的实现原理
    • 2.1 postProcessBeanDefinitionRegistry(registry)
    • Part 1 | configCandidates.add(new BeanDefinitionHolder(beanDef, beanName))
    • Part 2 | parser.parse(candidates)
    • Part 3 | this.reader.loadBeanDefinitions(configClasses)
      • 2.2 postProcessBeanFactory(beanFactory)
        • 3 总结
          • 参考文档
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档