Spring源码分析の配置类解析

news/2025/3/4 17:03:54/

文章目录

  • 前言
  • 一、processConfigBeanDefinitions
    • 1.1、checkConfigurationClassCandidate
    • 1.2、parse
      • 1.2.1、处理配置类标记了@Component 的情况
      • 1.2.2、处理 @ComponentScan 注解
  • 总结


前言

  在Spring的注解模式中,通常在构造AnnotationConfigApplicationContext时需要传入一个配置类,标注有扫描的类路径,以及@Configuration注解。
在这里插入图片描述  前篇中提到,解析配置类是在refresh方法的invokeBeanFactoryPostProcessors中,通过如图的三行关键代码,从容器中拿到ConfigurationClassPostProcessor后置处理器,并执行其中的postProcessBeanDefinitionRegistry方法完成的。
在这里插入图片描述invokeBeanFactoryPostProcessors
在这里插入图片描述ConfigurationClassPostProcessor 解析配置类

一、processConfigBeanDefinitions

  processConfigBeanDefinitions是进行配置类解析的关键逻辑。参数中会传入一个BeanDefinitionRegistry,其中就能拿到自定义的配置类:
在这里插入图片描述  processConfigBeanDefinitions方法的整体逻辑:

java">public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {// 存储符合条件的 @Configuration 类List<BeanDefinitionHolder> configCandidates = new ArrayList<>();// 获取当前 BeanDefinitionRegistry 中的所有 Bean 名称String[] candidateNames = registry.getBeanDefinitionNames();// 遍历所有 BeanDefinition,找出 @Configuration 类for (String beanName : candidateNames) {// 获取 BeanDefinitionBeanDefinition beanDef = registry.getBeanDefinition(beanName);// 检查该 BeanDefinition 是否已经被标记为 @Configuration 类if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {if (logger.isDebugEnabled()) {logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);}}// 1.1、checkConfigurationClassCandidate 判断当前 BeanDefinition 是否是 @Configuration 配置类else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {// 如果是 @Configuration 类,则添加到 configCandidates 列表configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// 如果没有发现 @Configuration 类,直接返回if (configCandidates.isEmpty()) {return;}// 按照 @Order 注解的值进行排序,保证 @Configuration 类按照顺序解析configCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});// 检测是否有自定义的 Bean名称 生成策略SingletonBeanRegistry sbr = null;if (registry instanceof SingletonBeanRegistry) {sbr = (SingletonBeanRegistry) registry;if (!this.localBeanNameGeneratorSet) {// 尝试从 SingletonBeanRegistry 获取 BeanName 生成器BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);if (generator != null) {this.componentScanBeanNameGenerator = generator;this.importBeanNameGenerator = generator;}}}// 如果环境变量 environment 为空,则使用默认的 StandardEnvironment// 在refresh的准备阶段设置过,通常不会为空if (this.environment == null) {this.environment = new StandardEnvironment();}// 创建 ConfigurationClassParser,用于解析 @Configuration 类ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);// 将 configCandidates 转换为 Set 以去重,并存入 candidatesSet<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);// 记录已经解析过的 @Configuration 类,防止重复解析Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());// 进入循环解析配置类,处理 @Import、@Bean、@ComponentScan 等do {StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");// 1.2、parse	解析 @Configuration 类parser.parse(candidates);parser.validate();// 获取解析出的 @Configuration 配置类Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());// 移除已经解析过的类,避免重复解析configClasses.removeAll(alreadyParsed);// 创建 ConfigurationClassBeanDefinitionReader 用于注册新的 BeanDefinitionif (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}// 解析 @Configuration 类中的 @Bean 方法,注册为 BeanDefinitionthis.reader.loadBeanDefinitions(configClasses);// 将新解析的 @Configuration 类加入已解析列表alreadyParsed.addAll(configClasses);processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();// 清空 candidates,准备检查是否有新加入的 @Configuration 类candidates.clear();// 检查在解析过程中是否新增了 BeanDefinitionif (registry.getBeanDefinitionCount() > candidateNames.length) {// 获取新的 BeanDefinition 名称String[] newCandidateNames = registry.getBeanDefinitionNames();// 记录原有的 BeanDefinition 名称Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));// 记录已经解析的 @Configuration 类名称Set<String> alreadyParsedClasses = new HashSet<>();for (ConfigurationClass configurationClass : alreadyParsed) {alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());}// 遍历新加入的 BeanDefinition,检查是否是 @Configuration 类for (String candidateName : newCandidateNames) {if (!oldCandidateNames.contains(candidateName)) {BeanDefinition bd = registry.getBeanDefinition(candidateName);// 只有未解析过的 @Configuration 类才会被加入解析队列if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&!alreadyParsedClasses.contains(bd.getBeanClassName())) {candidates.add(new BeanDefinitionHolder(bd, candidateName));}}}// 更新 candidateNames,保证下一轮解析可以获取新加入的 BeanDefinitioncandidateNames = newCandidateNames;}}while (!candidates.isEmpty()); // 如果还有新的 @Configuration 类,就继续解析// 注册 ImportRegistry,用于支持 @ImportAware 机制if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());}// 清理 MetadataReaderFactory 缓存,防止内存泄漏if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();}
}

1.1、checkConfigurationClassCandidate

  checkConfigurationClassCandidate方法的作用,是判断遍历到的类上,是否加入了@Configuration注解:
在这里插入图片描述关键代码,根据类的元信息判断是否有@Configuration注解

  其中的关键代码是这一段:

java">//根据类的元信息判断是否有@Configuration注解
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
//标记为full配置类
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
//标记为lite配置类
else if (config != null || isConfigurationCandidate(metadata)) {beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);

  @Configuration注解有一个proxyBeanMethods属性,默认为true:在这里插入图片描述在这里插入图片描述  如果@Configuration注解的proxyBeanMethods属性为true,该配置类就会被标记为full,否则是lite,这两种模式会影响 @Bean 方法的调用方式 和 Spring 是否使用 CGLIB 代理。

  • Full 模式下,Spring 使用 CGLIB 代理 来代理 @Configuration 配置类,使其成为一个 代理对象,该模式确保 @Bean 方法不会创建多个相同类型的实例
java">@Configuration(proxyBeanMethods = true) // Full 模式(默认)
public class AppConfig {@Beanpublic UserService userService() {//userService() 方法调用了 orderService() 方法。//orderService() 方法在 @Configuration 代理类内部被调用,因此 Spring 不会重新创建一个新的 OrderService,而是通过 代理方法 调用 Spring 容器中的 Bean,保证 userService 和其他 Bean 共享同一个 orderService 实例。return new UserService(orderService());}@Beanpublic OrderService orderService() {return new OrderService();}
}
  • Lite 模式则相反,Spring 不会为 @Configuration 配置类生成 CGLIB 代理,配置类就是一个普通的 Java 类。@Bean 方法不会走 Spring 容器,而是直接调用普通方法,因此可能会 创建多个实例,不能保证 @Bean 作用域的一致性。
java">@Configuration(proxyBeanMethods = false) // Lite 模式
public class AppConfig {@Beanpublic UserService userService() {return new UserService(orderService()); // 直接调用方法,不经过 Spring 代理}@Beanpublic OrderService orderService() {return new OrderService();}
}

1.2、parse

  parse是进行配置类解析的核心方法,最终调用的是ConfigurationClassParserdoProcessConfigurationClass,这里重点说明下处理@ComponentScan 注解的逻辑:

java">protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)throws IOException {// 1.2.1、 如果该配置类被 @Component 注解标记,则优先递归处理其内部类(嵌套类)if (configClass.getMetadata().isAnnotated(Component.class.getName())) {processMemberClasses(configClass, sourceClass, filter);}// 处理 @PropertySource 注解,加载外部属性配置文件for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class,org.springframework.context.annotation.PropertySource.class)) {if (this.environment instanceof ConfigurableEnvironment) {processPropertySource(propertySource);} else {logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +"]. Reason: Environment must implement ConfigurableEnvironment");}}// 1.2.2、处理 @ComponentScan 注解,扫描组件并注册到 Spring 容器Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);if (!componentScans.isEmpty() &&!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {for (AnnotationAttributes componentScan : componentScans) {// 解析 @ComponentScan 扫描的 BeanSet<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// 遍历扫描得到的 BeanDefinitionHolder,检查是否有新的 @Configuration 类并递归解析for (BeanDefinitionHolder holder : scannedBeanDefinitions) {BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();if (bdCand == null) {bdCand = holder.getBeanDefinition();}if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {parse(bdCand.getBeanClassName(), holder.getBeanName());}}}}// 处理 @Import 注解,导入其他配置类、ImportSelector 或 ImportBeanDefinitionRegistrar//如果是ImportSelector,那么调用执行selectImports方法得到类名,然后在把这个类当做配置类进行解析(递归)//如果是ImportBeanDefinitionRegistrar,那么则生成一个ImportBeanDefinitionRegistrar实例对象,并添加到配置类对象中(ConfigurationClass)的importBeanDefinitionRegistrars属性中processImports(configClass, sourceClass, getImports(sourceClass), filter, true);// 处理 @ImportResource 注解,导入 XML 配置文件AnnotationAttributes importResource =AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);if (importResource != null) {String[] resources = importResource.getStringArray("locations");Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");for (String resource : resources) {String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);configClass.addImportedResource(resolvedResource, readerClass);}}//处理 @Bean 方法,将其注册到配置类的 bean 方法集合中Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// 如果配置类实现了某些接口,则看这些接口内是否定义了@Bean的默认方法processInterfaces(configClass, sourceClass);// 处理当前类的超类(如果有),确保父类上的 @Configuration 也被解析if (sourceClass.getMetadata().hasSuperClass()) {String superclass = sourceClass.getMetadata().getSuperClassName();if (superclass != null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {this.knownSuperclasses.put(superclass, configClass);// 继续解析父类的配置return sourceClass.getSuperClass();}}// 没有超类需要处理,则返回 null,结束解析return null;
}

1.2.1、处理配置类标记了@Component 的情况

  如果一个 @Configuration 类同时被 @Component 注解标记,则意味着该类本身是一个 Spring 组件,并且可能包含内部类(嵌套类):

java">@Configuration
@Component
public class MainConfig {@Beanpublic String mainBean() {return "Main Bean";}@Configurationpublic static class NestedConfig {@Beanpublic String nestedBean() {return "Nested Bean";}}
}

在这里插入图片描述这种情况就需要进行递归处理

1.2.2、处理 @ComponentScan 注解

  在这一步,会去对于@ComponentScan 的属性进行解析:

在这里插入图片描述  在scanner.doScan方法中,即是去扫描类路径下标注了@Component及其衍生注解的类,生成bean定义。在Spring源码分析のBean扫描流程中有关于整个流程的说明。
在这里插入图片描述  但是有一种例外,即@ComponentScan 发现新的 @Configuration

java">@Configuration
@ComponentScan("com.example")
public class AppConfig { }

  com.example 目录下:

java">@Configuration
public class AdditionalConfig {@Beanpublic String myBean() {return "Hello";}
}

  AppConfig 触发@ComponentScan,扫描com.example目录时,发现AdditionalConfig是@Configuration,就会递归调用parse方法进行解析。

总结

  processConfigBeanDefinitions的主线流程:

  • 获取当前 BeanDefinitionRegistry 中的所有 Bean 名称。
  • 遍历这些bean名称,找到标注了 @Configuration注解的类。
  • 按照 @Order 注解(如果有)的值对标注了 @Configuration注解的类进行排序。
  • 创建 ConfigurationClassParser 解析器,用于解析。
  • 循环解析配置类
    • parse方法解析@Configuration
      • 如果该配置类被@Component注解标记,则优先递归处理其内部类(嵌套类)。
      • 处理@PropertySource注解,加载外部属性配置文件。
      • 处理@ComponentScan注解,扫描组件并注册到 Spring 容器。
      • 处理@Import注解
        • 如果是ImportSelector,那么调用执行selectImports方法得到类名,然后在把这个类当做配置类进行解析(递归)。
        • 如果是ImportBeanDefinitionRegistrar,那么则生成一个ImportBeanDefinitionRegistrar实例对象,并添加到配置类对象中(ConfigurationClass)的importBeanDefinitionRegistrars属性中。
      • 处理@ImportResource注解,导入 XML 配置文件。
      • 处理@Bean方法。
      • 处理实现了接口以及有父类的情况。
    • 解析@Configuration类中的@Bean方法,注册为 BeanDefinition。
    • 如果在解析的过程中,又产生了新的@Configuration类,则递归再次进行解析。
  • 注册 ImportRegistry,用于支持 @ImportAware 机制。
  • 进行最后的清理工作。


http://www.ppmy.cn/news/1576593.html

相关文章

vue全局注册组件

1、Vue.component 是 Vue 提供的一个全局 API&#xff0c;用于注册一个全局组件。这意味着你可以在应用的任何地方使用这个组件&#xff0c;而无需再次引入。 使用方法&#xff1a; import Vue from vue; import MyComponent from ./MyComponent.vue;// 注册全局组件 Vue.com…

游戏引擎学习第129天

仓库:https://gitee.com/mrxiao_com/2d_game_3 小妙招: vscode:定位错误行 一顿狂按F8 重构快捷键:F2 重构相关的变量 回顾并为今天的内容做准备 今天的工作主要集中在渲染器的改进上&#xff0c;渲染器现在运行得相当不错&#xff0c;得益于一些优化和组织上的改进。我们计…

蓝桥备赛(七)- 函数与递归(中)

一、函数重载 1.1 重载概念 引入&#xff1a; 比如&#xff1a;如果我们现在想要写一个函数 &#xff0c; 求两个整数的和 &#xff1a; #include <cstdio> #include <iostream> using namespace std;int IntAdd(int x, int y) {return x y; } int main() {in…

图数据库Neo4j面试内容整理-Cypher 查询优化

Cypher 查询优化 是在 Neo4j 中提高查询性能的关键部分。Cypher 是 Neo4j 的查询语言,允许我们通过图的结构进行高效的数据检索。然而,随着数据量的增大和查询复杂度的提高,查询性能可能会变差。为了优化 Cypher 查询,我们可以使用多种策略,包括合理设计查询、利用索引和约…

二氧化钛的制备:高科技背后的简单原理 京煌科技

二氧化钛是什么&#xff1f; 二氧化钛&#xff08;TiO₂&#xff09;是一种广泛应用的白色无机化合物&#xff0c;具有高折射率、强遮盖力和优异的化学稳定性。它不仅是涂料、塑料、纸张等行业的重要原料&#xff0c;还在化妆品、食品、医药等领域发挥着重要作用。本文将深入探…

详解DeepSeek模型底层原理及和ChatGPT区别点

一、DeepSeek大模型原理 架构基础 DeepSeek基于Transformer架构,Transformer架构主要由编码器和解码器组成,在自然语言处理任务中,通常使用的是Transformer的解码器部分。它的核心是自注意力机制(Self - Attention),这个机制允许模型在处理输入序列时,关注序列中不同位…

LeetCode 每日一题 2025/2/24-2025/3/2

记录了初步解题思路 以及本地实现代码&#xff1b;并不一定为最优 也希望大家能一起探讨 一起进步 目录 2/24 1656. 设计有序流2/25 2502. 设计内存分配器2/26 1472. 设计浏览器历史记录2/27 2296. 设计一个文本编辑器2/28 2353. 设计食物评分系统3/1 131. 分割回文串3/2 132. …

Spring Cloud — 消息驱动 Stream

Spring Cloud Stream 是让微服务更容易在应用中实现消息的发布和订阅处理的框架。Stream 支持与多种消息中间件整合&#xff0c;如Kafka、RibbitMQ等。 本文使用的是Kafka消息中间件&#xff0c;依赖文件为&#xff1a; <dependency><groupId>org.springframewor…