Spring Boot 实战之 EnableAutoConfiguration 实践和源码学习

 2022-08-20
原文地址:https://www.jianshu.com/p/582f48bea5a2

EnableAutoConfiguration 是SpringBoot的Enable系列中一个比较基础的的功能模块,现在我们就来学习如何使用,以及分析源码学习其工作原理

EnableAutoConfiguration 从名字也可以很容易看出来其功能是 能够自动装配配置 ,在SpringBoot中如果需要为其他人提供SDK等接口使用,使用方本身必须实例化接口类才可调用,如果每一个使用方都单独去实例化该接口,必然导致使用成本的增加,EnableAutoConfiguration就能很好的解决这个问题,使用方通过这个就可以直接使用,避免额外操作。

1、EnableAutoConfiguration 源码学习

先提个问题,如果现在只能使用Spring Framework,该如何实现类似的功能呢?
或许能想到的只有BPP,BeanPostProcessor或者BeanFactoryPostProcessor,只是他们处理的范畴不一样,BeanPostProcessor更多的是处理单个bean,而BeanFactoryPostProcessor是处理context上下文的

Spring包含了多种类型的BPP,在spring的生命周期的多个位置提供了对外的钩子便于扩展更多功能,关于BPP可以看看BPP的内容
在官方文档中,对BeanFactoryPostProcessor方法的简述也说的非常清楚, Modify the application context's internal bean factory after its standard initialization. All bean definitions will have been loaded, but no beans will have been instantiated yet. This allows for overriding or adding properties even to eager-initializing beans.

事实上,SpringBoot也确实是这样干的,ConfigurationClassPostProcessor 就是实例化了一个BeanDefinitionRegistryPostProcessor,从而拥有了修改context上下文的bean信息以及注册bean的功能

如下图由Spring的BPP处理器调用到ConfigurationClassPostProcessor,然后来到了AutoConfigurationImportSelector 类中

202208201733292761.png

image

1.1、ConfigurationClassPostProcessor 处理

先了解下ConfigurationClassPostProcessor 这个BPP是如何被注入到spring容器的

springboot启动学习笔记中 已经介绍了spring的context上下文创建是由context = createApplicationContext(); 实现的,深入该代码直到 AnnotationConfigUtils 类可以发现其操作是如果没发现org.springframework.context.annotation.internalCommonAnnotationProcessor这个bean的name,那就添加一个ConfigurationClassPostProcessor bean,具体如下图

202208201733333702.png

image

这样我们就清楚了ConfigurationClassPostProcessor 这个类是如何装载进spring容器中的,接下来就是ConfigurationClassPostProcessor这个类具体的调用操作了

ConfigurationClassPostProcessor 类

    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
        String[] candidateNames = registry.getBeanDefinitionNames();
    
        for (String beanName : candidateNames) {
               // 遍历spring容器所有的beanName信息,此时还未装载业务bean,
               // 只有spring&springboot本身框架层次需要的一些特定bean存在(特别注意包含主启动类)
               // 这点在之前的关于springboot的启动学习笔记中已经介绍了
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                    ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
                    // 确认该beandefinition是否存在org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass 键值对信息
                    // 如果存在则假定是已经经过配置类处理过了
                if (logger.isDebugEnabled()) {
                    logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                }
            }
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                   // 否则检查一下当前的beandefinition是否符合config 配置类
                  // 具体实现原理就是获取到bean的注解信息,然后查看是否存在 @Configuration 注解类或者 @Bean 注解
                  // 如果有,则返回true
                  // 当然在这里只会有主启动类才包含了@Configuration 的信息
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
    
        // Return immediately if no @Configuration classes were found
        if (configCandidates.isEmpty()) {
            return;
        }
        // 一般情况下,到这里只会有主启动类一个configCandidates信息存在
    
        // Sort by previously determined @Order value, if applicable
        Collections.sort(configCandidates, new Comparator<BeanDefinitionHolder>() {
            @Override
            public int compare(BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) {
                int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
                int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
                return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
            }
        });
    
        // Detect any custom bean name generation strategy supplied through the enclosing application context
        SingletonBeanRegistry sbr = null;
        if (registry instanceof SingletonBeanRegistry) {
            sbr = (SingletonBeanRegistry) registry;
            if (!this.localBeanNameGeneratorSet && sbr.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {
                 // 如果当前类不包含beanName 生成器 同时 context包含了单一的beanName生成器
                 // 设置当前bean的生成器信息
                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
                this.componentScanBeanNameGenerator = generator;
                this.importBeanNameGenerator = generator;
            }
        }
    
        // 生成配置解析类parses,开始解析每一个包含了@Configuration 的类
        ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, this.problemReporter, this.environment,
                this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    
        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
        Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
        do {
            parser.parse(candidates);
            // 关键的地方来了,这里就会去解析真正包含了@Configuration 的类
            parser.validate();
    
            Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
            // 所有通过@Configuration 装载进来的类集合
            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());
            }
            // reader是配置类装载类beandefinition
            this.reader.loadBeanDefinitions(configClasses);
            // 装载到Spring容器中,包含了那些使用@Bean的类信息
            alreadyParsed.addAll(configClasses);
                ......
                // 到这里就可以认为@Configuration 的导入基本完成了
    }

真正进入到@Configuration 解析的入口处代码

ConfigurationClassParser 类

202208201733359683.png

image

在经过processDeferredImportSelectors方法调用的之前,已经经过了parse处理,明确了@Configuration 包含的ImportSelectors 信息
如果需要自定义该注解则一定也要实现利用@Import注解导入的ImportSelector 实现类

    private void processDeferredImportSelectors() {
        List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
        this.deferredImportSelectors = null;
        Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);
        // 对获取的DeferredImportSelectorHolder 排序后进行遍历操作
    
        for (DeferredImportSelectorHolder deferredImport : deferredImports) {
            ConfigurationClass configClass = deferredImport.getConfigurationClass();
            // configClass 就是主启动类
            try {
                String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
                // 所有的需要导入的import类的selectImports方法执行,
                // 这个里面就是EnableAutoConfigurationImportSelector 
                // 具体的EnableAutoConfigurationImportSelector里面的selectImports后面说
                processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
                // 把获取的配置类信息进一步迭代处理,因为存在在类中包含了配置类的情况
                // 不过需要注意,这时候并未往spring容器中注入
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                        configClass.getMetadata().getClassName() + "]", ex);
            }
        }
    }

1.2、EnableAutoConfigurationImportSelector 的 selectImports 执行

来到AutoConfigurationImportSelector类

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
              // 如果注解原信息未激活,则不可用,直接返回空
            return NO_IMPORTS;
        }
        try {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                    .loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
            List<String> configurations = getCandidateConfigurations(annotationMetadata,
                    attributes);
            // 利用SpringFactoriesLoader 获取系统中所有的META-INF/spring.factories 的 EnableAutoConfiguration 的键值对信息,其中就包含了上面我们自定义的类信息
            configurations = removeDuplicates(configurations);
            // 存在多处地方可能注册了相同的类信息,去重处理
            configurations = sort(configurations, autoConfigurationMetadata);
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            // 匹配出包含exclude、excludeName 的列表信息,后续移除该config
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = filter(configurations, autoConfigurationMetadata);
            // 总之经过各种操作,最后产出了可用的配置类列表
            fireAutoConfigurationImportEvents(configurations, exclusions);
            // 配置导入的事件触发
            return configurations.toArray(new String[configurations.size()]);
            // 返回最后的配置类列表
        }
        catch (IOException ex) {
            throw new IllegalStateException(ex);
        }
    }

1.3、总结

到这里对EnableAutoConfiguration的源码学习就算是结束了,利用Spring对外的BeanFactoryPostProcessor的钩子ConfigurationClassPostProcessor去解析出@Import引入的类,然后解析出所有被@Configuration的对象,然后注册到spring容器中,这样就完成了整个的自动装载过程

2、DEMO

202208201733378744.png

image

如上图,可以发现在资源根目录下存放了一个"META-INF/spring.factories"的文件,里面的内容也很简单

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.demo.boot.api.CustomAutoConfiguration

CustomAutoConfiguration 类代码

    @Configuration
    public class CustomAutoConfiguration {
    
        @Bean
        public ApiStudent apiStudent() {
            ApiStudent apiStudent = new ApiStudent();
            // ApiStudent 只有一个name字段的基础POJO
            apiStudent.setName("auto-config");
            return apiStudent;
        }
    
    }

非常简单的一个注册bean到spring的样例

现在我们在另一个服务中引入该服务,然后直接通过@resource 注解就可以引用到ApiStudent这个bean了

    @Resource
    private ApiStudent apiStudent;
    
    @GetMapping("/custom-autoconfig")
    @ResponseBody
    public String autoConfig() {
        return apiStudent.getName();
    }

202208201733395345.png

image

可以看出网页上输出的内容就是起初在CustomAutoConfiguration中为apiStudent这个bean设置的属性值

到此demo就结束了,非常的基础的一个样例,在实际应用中也是如此,也使用的非常频繁。