Spring - @Import注解

文章目录

  • 基本用法
  • 源码分析
    • ConfigurationClassPostProcessor
      • ConfigurationClass
    • SourceClass
    • getImports
    • processImports
      • 处理 ImportSelector
        • ImportSelector 接口
        • DeferredImportSelector
      • 处理 ImportBeanDefinitionRegistrar
        • ImportBeanDefinitionRegistrar 接口
      • 处理Configuration

本文源码基于spring-context-5.3.36 版本

基本用法

  1. 直接填class数组方式
    @Import注解填入要导入的类的Class即可
@Import({ A.class , B.class... })
@Configuration
public class TestDemo {}
  1. ImportSelector方式
    实现ImportSelector接口,selectImport返回需要导入的类的全限定名称
public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {return new String[0];}
}

然后@Import中指定ImportSelector的实现类的Class

  1. ImportBeanDefinitionRegistrar方式
    实现ImportBeanDefinitionRegistrar接口
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {// 通过 BeanDefinitionRegistry 构造 BeanDefinition 并进行注册}
}

然后@Import中指定ImportBeanDefinitionRegistrar的实现类的Class
总结

  1. 以上三种用法方式皆可混合在一个@Import中使用,注意第一种和第二种都是以全类名的方式注册,而第三中可自定义名称
  2. 第一种最简单,第二种较简单,第三种需要操作BeanDefinition,较为复杂
  3. @Import注解不一定非要和@Configuration搭配使用,也可以和@Component等注解使用,效果一样
@Import({ A.class , B.class... })
@Component
public class TestDemo {}

源码分析

入口

BeanDefinitionRegistryPostProcessor执行阶段生效

例如 ConfigurationClassPostProcessor

ConfigurationClassPostProcessor

ConfigurationClass

代表定义的@Configuration修饰的类,包含一些bean方法。配置加载都有一个解析过程,对ConfigurationClass的解析就是由ConfigurationClassParser#parse方法完成的,它会解析每个配置类上的配置,包括@Import注解这个配置

public void parse(Set<BeanDefinitionHolder> configCandidates) {// 拿到所有的BeanDefinitionfor (BeanDefinitionHolder holder : configCandidates) {BeanDefinition bd = holder.getBeanDefinition();try {if (bd instanceof AnnotatedBeanDefinition) {parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());}else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());}else {parse(bd.getBeanClassName(), holder.getBeanName());}}catch (BeanDefinitionStoreException ex) {...}}this.deferredImportSelectorHandler.process();
}

在这里插入图片描述

SourceClass

org.springframework.context.annotation.ConfigurationClassParser.SourceClass主要用于处理和解析配置类的源信息

private class SourceClass implements Ordered {// Class或者MetadataReaderprivate final Object source;// 注解元数据private final AnnotationMetadata metadata;
}

主要作用如下:

  1. 表示源类:SourceClass 代表 Spring 配置类中的一个类,它封装了获取该类相关的源信息的方法,比如类的名称、注解、方法等。
  2. 提供元信息:SourceClass 提供了一些元数据的方法,帮助开发者获取类的详细信息,比如父类、实现的接口、注解信息等。

getImports

SourceClass可以简单的理解为java里的Class,同样,SourceClass可以代表普通类,也可以代表注解。同时SourceClass携带有一些注解元数据信息。其实getImports方法的过程就和根据反射获取一个类上的所有注解(包括修饰注解的注解)这个过程差不多

使用深度优先遍历

  1. 首先定义一个visited记录已经访问过的 SourceClass
  2. 对于每个访问的 SourceClass ,如果它被@Import注解修饰,则获取@Import注解的属性值
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {Set<SourceClass> imports = new LinkedHashSet<>();Set<SourceClass> visited = new LinkedHashSet<>();collectImports(sourceClass, imports, visited);return imports;
}

调用的collectImports()方法是一个递归操作,从第一个SourceClass开始,获取其所有注解,然后调用collectImports递归进行收集。因为@Import可能放在注解上形成复合注解

private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)throws IOException {if (visited.add(sourceClass)) {for (SourceClass annotation : sourceClass.getAnnotations()) {String annName = annotation.getMetadata().getClassName();if (!annName.equals(Import.class.getName())) {collectImports(annotation, imports, visited);}}imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));}
}

最后返回的imports就是从根SourceClass开始遍历的所有@Import注解的值,这个值是一个java中Class的集合

processImports

getImports的返回值作为processImports方法的第三个参数

/*** * @param configClass	配置类,一般是@Configuration修饰的类* @param currentSourceClass  扫描@Import注解的根节点* @param importCandidates	  currentSourceClass上携带的所有@Import注解的属性值* @param exclusionFilter    用于排除* @param checkForCircularImports  是否检查@Import形成环的情况*/
private void processImports(ConfigurationClass configClass,SourceClass currentSourceClass,Collection<SourceClass> importCandidates,Predicate<String> exclusionFilter,boolean checkForCircularImports) {if (importCandidates.isEmpty()) {return;}// 检测可能存在的循环@Import情况if (checkForCircularImports && isChainedImportOnStack(configClass)) {this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));} else {// 下面代码保留了整体结构,省略了不重要的细节this.importStack.push(configClass);try {for (SourceClass candidate : importCandidates) {// 一些if/else判断if (candidate.isAssignable(ImportSelector.class)) {...} else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {...} else {// 既不是ImportSelector也不是ImportBeanDefinitionRegistrar// 当作@Configuration类处理this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());// 继续走处理配置类的流程,会继续进行processImports方法processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);}}} catch (BeanDefinitionStoreException e) {// 省略}finally {this.importStack.pop();}}
}

处理 ImportSelector

如果是ImportSelector

处理细节如下所示

// 实例化ImportSelector对象
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry);
// 确定过滤器
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {exclusionFilter = exclusionFilter.or(selectorFilter);
}// 区分是否是DeferredImportSelector
if (selector instanceof DeferredImportSelector) {this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
} else {// 所有导入的类名称String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());// 将ImportSelector返回的类名转换为SourceClass// 使用Class.forName转换成Class实例,构造SourceClass对象Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);// 递归调用processImportsprocessImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
ImportSelector 接口
public interface ImportSelector {/*** 根据配置类的元数据信息,返回类的全限定名称* importingClassMetadata:@Import注解修饰的那个类**/String[] selectImports(AnnotationMetadata importingClassMetadata);@Nullabledefault Predicate<String> getExclusionFilter() {return null;}
}
DeferredImportSelector

Deferred意思是延迟,相比于ImportSelector会立即被处理,DeferredImportSelector的selectImport方法会在当前配置类所有的Bean信息解析完毕后才进行处理
在这里插入图片描述
DeferredImportSelector的作用是用于调整Import的Bean和当前配置类配置的Bean的先后关系

因为有条件注解的存在,所以需要Bean之间的注册有先后关系,条件注解才能发挥作用

处理 ImportBeanDefinitionRegistrar

如果SourceClass类型是ImportBeanDefinitionRegistrar的实现类简单,则调用org.springframework.context.annotation.ConfigurationClass#addImportBeanDefinitionRegistrar方法

// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
ImportBeanDefinitionRegistrar 接口

ImportBeanDefinitionRegistrar接口用于手动注册BeanDefinition

public interface ImportBeanDefinitionRegistrar {/*** 注册BeanDefinition* BeanDefinitionRegistryPostProcessor此时还未注册* @param importingClassMetadata 导入的类的注解元数据* @param registry 当前BeanDefinitionRegistry,即BeanFactory实现* @param importBeanNameGenerator 导入的Bean的命名策略*/default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {registerBeanDefinitions(importingClassMetadata, registry);}default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}
}
  1. BeanNameGenerator:默认实现是ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR或者通过ConfigurationClassPostProcessor#setBeanNameGenerator进行设置
  2. BeanDefinitionRegistry registry:其实就是BeanFactory

@Configuration配置类解析完毕后,下一步就是将让配置生效。如下图所示,通过ConfigurationClassBeanDefinitionReader来加载BeanDefinition
在这里插入图片描述
ConfigurationClassBeanDefinitionReader加载BeanDefinition的过程中条件注解会生效

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();for (ConfigurationClass configClass : configurationModel) {loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);}
}private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {if (trackedConditionEvaluator.shouldSkip(configClass)) {String beanName = configClass.getBeanName();if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {this.registry.removeBeanDefinition(beanName);}this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());return;}// 处理已经导入的配置类if (configClass.isImported()) {registerBeanDefinitionForImportedConfigurationClass(configClass);}// 配置类的所有@Bean修饰的方法for (BeanMethod beanMethod : configClass.getBeanMethods()) {loadBeanDefinitionsForBeanMethod(beanMethod);}// @ImportedResources用于导入Spring的配置文件// 而Spring的配置文件中也可以定义BeanloadBeanDefinitionsFromImportedResources(configClass.getImportedResources());// 加载ImportBeanDefinitionRegistrar中需要注册的BeanloadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

处理Configuration

如果@Import的内容既不是ImportSelector也不是ImportBeanDefinitionRegistrar,那么就把它当作@Configuration修饰的类进行处理,无论它是不是真的被@Configuration修饰

// 既不是ImportSelector也不是ImportBeanDefinitionRegistrar
// 当作@Configuration类处理
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
// 继续走处理配置类的流程,会继续进行processImports方法
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);

假如@Import的是一个普通类,比如下面这样,什么都没有。由于@Import的存在,SimpleClass还是会被当作Bean注册进容器

public class SimpleClass {
}

如果给它加上@Configuration配置类可以有的配置,它也是会生效的

public class SimpleClass {@Beanpublic A a() {return new A();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1550971.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

2-3树(2-3 Tree):原理、常见算法及其应用

目录 引言 2-3树的基本概念 常见算法 查找节点 插入节点 删除节点 2-3树的应用场景 1. 文件系统目录管理 应用原理 场景描述 2. 字典编码 应用原理 场景描述 总结 优势对比 自平衡特性 灵活的节点结构 高效的操作性能 简单的实现 广泛的应用场景 数据一致…

遥感图像分割

遥感图像分割是一种应用于遥感图像的计算机视觉技术&#xff0c;用于将图像划分为不同的区域&#xff0c;每个区域代表地表的不同特征&#xff0c;如水体、森林、城市区域等。这种分割帮助我们更好地理解和分析地球表面的变化&#xff0c;对于环境监测、城市规划、农业、灾害管…

AR技术在电商行业的应用及优势有哪些?

AR&#xff08;增强现实&#xff09;技术在电商行业的应用广泛且深入&#xff0c;为消费者带来了全新的购物体验&#xff0c;同时也为商家带来了诸多优势。以下是AR技术在电商行业的主要应用场景及其优势&#xff1a; 一、应用场景 1、虚拟商品展示与试用 家具AR摆放&#x…

济南站活动回顾|IvorySQL中的Oracle XML函数使用示例及技术实现原理

近日&#xff0c;由中国开源软件推进联盟PG分会 & 齐鲁软件园联合发起的“PostgreSQL技术峰会济南站”在齐鲁开源社举办。瀚高股份IvorySQL作为合作伙伴受邀参加此次活动。 瀚高股份IvorySQL技术工程师 向逍 带来「IvorySQL中的Oracle XML函数兼容」的议题分享。在演讲中&a…

前端vue-form表单的验证

form表单验证的完整步骤

二叉树的中序遍历(java)

概述 关于二叉树&#xff0c;我们都不陌生&#xff0c;许多基于递归的问题发起点都是一个二叉树的root节点。对于各种二叉树的问题&#xff0c;我们也是通过dfs进行求解。例如求二叉树的深度、最近公共祖先等 算法分析 关于二叉树的中序遍历&#xff0c;我们都知道应该先访…

【C++单调队列】1438. 绝对差不超过限制的最长连续子数组|1672

本文时间知识点 C队列、双向队列 LeetCode1438. 绝对差不超过限制的最长连续子数组 给你一个整数数组 nums &#xff0c;和一个表示限制的整数 limit&#xff0c;请你返回最长连续子数组的长度&#xff0c;该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。 如…

Flume实战--Flume中的选择器、自动容灾(故障转移)、负载均衡的详解与操作

本文详细介绍了Apache Flume的关键特性&#xff0c;包括选择器、拦截器、故障转移和负载均衡。选择器负责将数据分发到多个Channel&#xff0c;拦截器用于修改或丢弃Event。故障转移机制能够在Sink故障时自动切换&#xff0c;而负载均衡则在多个Sink间分配负载。文章还提供了自…

CANoe_DBC能够打开但是无法使用“BusType”

解决DBC文件在CAPL中调用问题&#xff1a;从CANdb到CAPL的顺畅过渡 在汽车电子和嵌入式系统开发中&#xff0c;DBC&#xff08;Database CAN&#xff09;文件作为描述CAN&#xff08;Controller Area Network&#xff09;通信协议的重要工具&#xff0c;广泛应用于网络设计、测…

工作日志:ruoyi-vue-plus echarts根据窗口大小变化

1、echarts根据窗口大小变化。 onMounted(() > {// 折线图type EChartsOption echarts.EChartsOption;var chartDom document.getElementById(chartDom)!;var myChart echarts.init(chartDom);var option: EChartsOption;option {grid: {left: 35,top: 10,bottom: 30,r…

jenkins部署Maven和NodeJS项目

在 Java 项目开发中&#xff0c;项目的编译、测试、打包等是比较繁琐的&#xff0c;属于重复劳动的工作&#xff0c;浪费人力和时间成本。以往开发项目时&#xff0c;程序员往往需要花较多的精力在引用 jar 包搭建项目环境上&#xff0c;跨部门甚至跨人员之间的项目结构都有可能…

1.8 软件业务测试

欢迎大家订阅【软件测试】 专栏&#xff0c;开启你的软件测试学习之旅&#xff01; 文章目录 前言1 概述2 方法3 测试策略4 案例分析 前言 在软件开发生命周期中&#xff0c;业务测试扮演着至关重要的角色。本文详细讲解了业务测试的定义、目的、方法以及测试策略。 本篇文章参…

C++队列、双向队列

前言 C算法与数据结构 打开打包代码的方法兼述单元测试 队列 队列&#xff08;Queue&#xff09;是一种基本的线性数据结构&#xff0c;它遵循先进先出&#xff08;First In First Out, FIFO&#xff09;的原则。这意味着最先被添加到队列中的元素将会是最先被移除的。和生活…

命令回显echo

命令回显 通常&#xff0c;make在执行命令行之前会把要执行的命令行进行输出。我们称之为“回显”&#xff0c;就好像我们输入命令执行一样。 如果要执行的命令行以字符“”开始&#xff0c;则make在执行时这个命令就不会被回显。典型的用法是我们在使用“echo”命令输出一些信…

Github 2024-09-29 php开源项目日报 Top10

根据Github Trendings的统计,今日(2024-09-29统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目10Blade项目1Java项目1ASP项目1Coolify: 开源自助云平台 创建周期:1112 天开发语言:PHP, Blade协议类型:Apache License 2.0Star数量…

Java多线程几个哈希表的区别

HashMap 首先HashMap肯定是不行的,并没有加解锁操作,一旦多线程同时写的话,直接就会发生覆盖之类的操作 排除HashMap先,主要对比HashTable和ConcurrentHashMap HashTable vs ConcurrentHashMap 1. 加锁粒度不同 HashTable HashTable是对整个哈希表进行加锁操作,任何增删改查操…

数据结构串的kmp相关(求next和nextval)

傻瓜版&#xff0c;用来演示手算过程&#xff0c;个人理解用的&#xff0c;仅供参考。

CICD Jenkins实现Pipline

一、安装 1、由于 Jenkins 是基于 Java 的&#xff0c;首先需要确保你的系统中安装了 Java。推荐使用 OpenJDK 11。可以通过以下命令安装&#xff1a; apt update apt install openjdk-11-jdk2、在安装 Jenkins 之前&#xff0c;你需要将其仓库添加到你的系统中。首先&#x…

DotNetty ChannelRead接收数据为null

问题&#xff1a;C#使用Dotnetty和Java netty服务器通讯&#xff0c;结果能正确发送数据到服务器&#xff0c;却始终接收不到服务器返回的数据。 解决&#xff1a;一定一定要注意服务器和客户端使用的编码一定要完全一样才行 我先前在客户端添加了StringDecoder,服务器却没有…

【Spring Boot 入门一】构建你的第一个Spring Boot应用

一、引言 在当今的软件开发领域&#xff0c;Java一直占据着重要的地位。而Spring Boot作为Spring框架的延伸&#xff0c;为Java开发者提供了一种更加便捷、高效的开发方式。它简化了Spring应用的搭建和配置过程&#xff0c;让开发者能够专注于业务逻辑的实现。无论是构建小型的…