Spring源码分析の配置类解析

ops/2025/3/3 18:25:07/

文章目录

  • 前言
  • 一、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/ops/162833.html

相关文章

【LLM】DeepSeek开源技术汇总

note 一、FlashMLA&#xff1a;MLA解码内核 二、DeepEP&#xff1a;针对MoE和EP的通信库 三、DeepGEMM&#xff1a;FP8 通用矩阵乘法&#xff08;GEMM&#xff09;库 四、DualPipe、EPLB&#xff1a;双向管道并行算法 五、3FS&#xff1a;一种高性能分布式文件系统 文章目录 n…

神经网络中的Nesterov Momentum

Nesterov Accelerated Gradient (NAG)&#xff0c;也称为Nesterov Momentum&#xff0c;是一种改进版的动量优化算法&#xff0c;旨在加速梯度下降过程中的收敛速度&#xff0c;并提高对最优解的逼近效率。它由Yurii Nesterov在1983年提出&#xff0c;是对传统动量方法的一种增…

Idea 和 Pycharm 快捷键

一、快捷键 二、Pycharm 中怎么切换分支 参考如下 如果在界面右下角 没有看到当前所在的分支&#xff0c;如 “Git:master” 3. 有了 4.

Hive之正则表达式RLIKE详解及示例

目录 一、RLIKE 语法及核心特性 1. 基本语法 2. 核心特性 二、常见业务场景及示例 场景1&#xff1a;过滤包含特定模式的日志&#xff08;如错误日志&#xff09; 场景2&#xff1a;验证字段格式&#xff08;如邮箱、手机号&#xff09; 场景3&#xff1a;提取复杂文本中…

论文笔记-NeurIPS2017-DropoutNet

论文笔记-NeurIPS2017-DropoutNet: Addressing Cold Start in Recommender Systems DropoutNet&#xff1a;解决推荐系统中的冷启动问题摘要1.引言2.前言3.方法3.1模型架构3.2冷启动训练3.3推荐 4.实验4.1实验设置4.2在CiteULike上的实验结果4.2.1 Dropout率的影响4.2.2 实验结…

计算机毕业设计Hadoop+Spark+DeepSeek-R1大模型音乐推荐系统 音乐数据分析 音乐可视化 音乐爬虫 知识图谱 大数据毕业设计

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

Vue2学习

一、Vue3 基础 监视属性 天气案例 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>天气案例</…

在Pycharm中将ui文件修改为py文件

在Pycharm中将ui文件修改为py文件 有些时候&#xff0c;我们需要把QTDesigner生成的.ui文件修改为.py文件 在一些教程中&#xff0c;通常使用cmd打开终端修改&#xff0c;或者是有一些人写了一些脚本来修改 这里我们可以使用pycharm来快速的修改 首先&#xff0c;我们在pyc…