• Spring源码分析——Configuration配置类解析流程


    示例工程

    引入Maven依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.5</version>
        </dependency>
    </dependencies>
    

    在项目中新建一个byx.test包,然后添加以下三个类:

    public class A {
    }
    
    public class B {
    }
    
    @Configuration
    public class MyConfig {
        @Bean
        public A a() {
            return new A();
        }
    
        @Bean
        public B b() {
            return new B();
        }
    }
    

    再添加一个Main类作为启动类:

    public class Main {
        public static void main(String[] args) {
            // 初始化容器
            ApplicationContext ctx = new AnnotationConfigApplicationContext("byx.test");
            
            // 输出容器中所有bean的name
            for (String name : ctx.getBeanDefinitionNames()) {
                System.out.println(name);
            }
        }
    }
    

    运行main方法,控制台输出如下:

    org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    org.springframework.context.event.internalEventListenerProcessor
    org.springframework.context.event.internalEventListenerFactory
    myConfig
    a
    b
    

    可以看到,容器中一共有7个bean。前面4个org开头的是Spring内部的组件,myConfig是我们定义的配置类MyConfigabMyConfig使用Bean注解导入的bean。

    下面就来探究一下Spring是如何处理配置类的。

    ConfigurationClassPostProcessor执行流程

    配置类处理的入口是在ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry方法,实现如下:

    public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
    		PriorityOrdered, ResourceLoaderAware, ApplicationStartupAware, BeanClassLoaderAware, EnvironmentAware {
        ...
        @Override
    	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    		int registryId = System.identityHashCode(registry);
    		if (this.registriesPostProcessed.contains(registryId)) {
    			throw new IllegalStateException(
    					"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
    		}
    		if (this.factoriesPostProcessed.contains(registryId)) {
    			throw new IllegalStateException(
    					"postProcessBeanFactory already called on this post-processor against " + registry);
    		}
    		this.registriesPostProcessed.add(registryId);
    
            // 处理配置类的注册
    		processConfigBeanDefinitions(registry);
    	}
        ...
    }
    

    ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor,根据Spring的生命周期,里面的postProcessBeanDefinitionRegistry方法会在容器初始化时被回调。

    postProcessBeanDefinitionRegistry方法中,真正的逻辑是在最后一行processConfigBeanDefinitions(registry)调用。这个方法十分复杂,它包含了完整的配置类解析逻辑,下面来一点点分析。

    // 用来保存所有配置类的定义
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    

    这里首先创建了一个configCandidates列表,用来保存容器中的所有配置类,即所有被Configuration注解标注的类。由于Configuration注解被Component注解修饰,所以所有的配置类在AnnotationConfigApplicationContext初始化过程中就已经被注册到容器中了。

    // 当前容器中所有bean的name
    String[] candidateNames = registry.getBeanDefinitionNames();
    
    // 遍历容器中的所有bean
    // 如果满足配置类的条件(ConfigurationClassUtils.checkConfigurationClassCandidate返回true),则加入configCandidates
    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));
        }
    }
    

    上面的这个for循环用来遍历当前容器中的所有bean,如果满足配置类的条件,则加入到configCandidates中。那么是如何判断一个bean是不是配置类呢?从代码中可以看到,是通过调用ConfigurationClassUtils.checkConfigurationClassCandidate方法来判断的。

    ConfigurationClassUtils.checkConfigurationClassCandidate方法实现如下:

    public static boolean checkConfigurationClassCandidate(
            BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
        ...
    
        AnnotationMetadata metadata;
        // 获取beanDef的注解元数据
        ...
    
        // 获取Configuration注解的属性值
        Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
        // 重量级配置类:为配置类的每个方法都生成代理,防止配置类内部的方法相互调用时产生问题
        if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
        }
        // 轻量级配置类:不为配置类生成代理
        else if (config != null || isConfigurationCandidate(metadata)) {
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
        }
        else {
            return false;
        }
    
        // 能执行到这里说明是一个配置类,下面判断是否需要进行顺序处理
        Integer order = getOrder(metadata);
        if (order != null) {
            beanDef.setAttribute(ORDER_ATTRIBUTE, order);
        }
    
        return true;
    }
    
    public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
        // 忽略接口类型
        if (metadata.isInterface()) {
            return false;
        }
    
        // 是否被若干种指定注解之一标注?
        // candidateIndicators中包含了Component、ComponentScan、Import、ImportResource这几个注解
        for (String indicator : candidateIndicators) {
            if (metadata.isAnnotated(indicator)) {
                return true;
            }
        }
    
        // 看看是否有被Bean注解标注的方法
        return hasBeanMethods(metadata);
    }
    

    ConfigurationClassUtils.checkConfigurationClassCandidate方法的逻辑比较简单,核心就是判断类上面有没有标注Configuration注解,同时还包含重量级配置类和轻量级配置类的处理。

    回到ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry方法。获取了所有配置类后,接下来进行一些额外处理:

    // 如果不存在配置类,则提前返回
    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);
    });
    

    在阅读下面的代码之前,先来介绍一下Spring中与配置类相关的几个重要组件:

    • ConfigurationClassParser:将配置类的BeanDefinitionHolder转换成ConfigurationClass
    • ConfigurationClass:Spring内部用来封装配置类相关信息的包装类,包括对所有Bean方法的封装
    • ConfigurationClassBeanDefinitionReader:用来注册配置类内部被Bean注解标注的方法声明的bean,内部包括对条件装配以及各种导入的的处理

    下面是配置类处理的核心代码:

    // 创建ConfigurationClassParser
    ConfigurationClassParser parser = new ConfigurationClassParser(
            this.metadataReaderFactory, this.problemReporter, this.environment,
            this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    
    // candidates保存当前待处理的配置类
    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    
    // alreadyParsed保存已处理的配置类
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    
    do {
        // 使用parser将candidates中的配置类definition转换成ConfigurationClass
        StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
        parser.parse(candidates);
        parser.validate();
        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    
        configClasses.removeAll(alreadyParsed);
    
        // 创建ConfigurationClassBeanDefinitionReader
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                    registry, this.sourceExtractor, this.resourceLoader, this.environment,
                    this.importBeanNameGenerator, parser.getImportRegistry());
        }
    
        // 使用reader注册配置类中定义的组件
        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());
    

    上面的代码就是用了前面提到的三个关键组件来对配置类进行解析的,首先调用ConfigurationClassParserparse方法将配置类的BeanDefinitionHolder转换成ConfigurationClass,然后传入ConfigurationClassBeanDefinitionReaderloadBeanDefinitions方法执行真正的注册操作。如果使用调试器可以发现,当执行完this.reader.loadBeanDefinitions(configClasses)这行代码后,当前容器的beanDefinitionMap的大小增加了。

    上面的代码还包含了一个do-while循环,这个循环用来一遍又一遍地解析新增的配置类,因为一个配置类可能会用Bean注解导入另一些配置类,这些新增的配置类会在下一轮循环被解析,直到没有新增的配置类。

    到这里,配置类处理的大致流程就分析完了。

  • 相关阅读:
    程序片段--2的乘方
    Set、Map集合、栈、队列
    Map迭代(六种)
    Struts2标签--控制标签
    线性表
    数据结构笔记(1)
    spingMVC问题小结
    《浪潮之巅》十四章笔记
    《浪潮之巅》十三章笔记
    《浪潮之巅》十二章笔记
  • 原文地址:https://www.cnblogs.com/baiyuxuan/p/14957443.html
Copyright © 2020-2023  润新知