Spring源码分析——Configuration配置类解析流程
2021/7/1 11:22:51
本文主要是介绍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
是我们定义的配置类MyConfig
,a
和b
是MyConfig
使用Bean
注解导入的bean。
下面就来探究一下Spring是如何处理配置类的。
ConfigurationClassPostProcessor执行流程
配置类处理的入口是在ConfigurationClassPostProcessor
的postProcessBeanDefinitionRegistry
方法,实现如下:
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
注解,同时还包含重量级配置类和轻量级配置类的处理。
回到ConfigurationClassPostProcessor
的postProcessBeanDefinitionRegistry
方法。获取了所有配置类后,接下来进行一些额外处理:
// 如果不存在配置类,则提前返回 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());
上面的代码就是用了前面提到的三个关键组件来对配置类进行解析的,首先调用ConfigurationClassParser
的parse
方法将配置类的BeanDefinitionHolder
转换成ConfigurationClass
,然后传入ConfigurationClassBeanDefinitionReader
的loadBeanDefinitions
方法执行真正的注册操作。如果使用调试器可以发现,当执行完this.reader.loadBeanDefinitions(configClasses)
这行代码后,当前容器的beanDefinitionMap
的大小增加了。
上面的代码还包含了一个do-while循环,这个循环用来一遍又一遍地解析新增的配置类,因为一个配置类可能会用Bean
注解导入另一些配置类,这些新增的配置类会在下一轮循环被解析,直到没有新增的配置类。
到这里,配置类处理的大致流程就分析完了。
这篇关于Spring源码分析——Configuration配置类解析流程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-27消息中间件底层原理资料详解
- 2024-11-27RocketMQ底层原理资料详解:新手入门教程
- 2024-11-27MQ底层原理资料详解:新手入门教程
- 2024-11-27MQ项目开发资料入门教程
- 2024-11-27RocketMQ源码资料详解:新手入门教程
- 2024-11-27本地多文件上传简易教程
- 2024-11-26消息中间件源码剖析教程
- 2024-11-26JAVA语音识别项目资料的收集与应用
- 2024-11-26Java语音识别项目资料:入门级教程与实战指南
- 2024-11-26SpringAI:Java 开发的智能新利器