当前位置: 首页 > news >正文

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();}
}

相关文章:

Spring - @Import注解

文章目录 基本用法源码分析ConfigurationClassPostProcessorConfigurationClass SourceClassgetImportsprocessImports处理 ImportSelectorImportSelector 接口DeferredImportSelector 处理 ImportBeanDefinitionRegistrarImportBeanDefinitionRegistrar 接口 处理Configuratio…...

新能源汽车储充机器人:能源高效与智能调度

新能源汽车储充机器人&#xff1a;开启能源高效利用与智能调度的未来之门 随着全球能源危机的日益加剧和环境污染问题的不断恶化&#xff0c;新能源汽车成为了未来交通领域的重要发展方向。然而&#xff0c;新能源汽车的普及不仅需要解决电池技术的瓶颈&#xff0c;还需要构建一…...

【Linux网络】详解TCP协议(2)

&#x1f389;博主首页&#xff1a; 有趣的中国人 &#x1f389;专栏首页&#xff1a; Linux网络 &#x1f389;其它专栏&#xff1a; C初阶 | C进阶 | 初阶数据结构 小伙伴们大家好&#xff0c;本片文章将会讲解 TCP协议的三次握手和四次挥手 的相关内容。 如果看到最后您觉得…...

STM32DMA学习日记

STM32 DMA学习日记 写于2024/9/28晚 文章目录 STM32 DMA学习日记1. DMA简介2. I/O方式2.1 程序查询方式2.2 程序中断方式2.3 DMA方式 3.DMA框图4. 相关寄存器4.1 DMA中断状态寄存器&#xff08;DMA_ISR&#xff09;4.2 DMA中断标志清除寄存器&#xff08;DMA_IFCR&#xff09;…...

【高性能内存池】page cache 5

page cache 1 page cache的框架2 central cache从page cache申请n页span的过程3 page cache 的结构3.1 page cache类框架3.2 central cache向page cache申请span3.3 获取k页的span page cache的结构和central cache是一样的&#xff0c;都是哈希桶的结构&#xff0c;并且挂载的…...

Vue 3 魔法揭秘:CSS 解析与 scoped 背后的奇幻之旅

文章目录 一、背景二、源码分析transformMain 返回值transformStyle 方法compileStyleAsync 方法scopedPlugin 方法template 添加 __scopeId 三、总结 一、背景 Vue 3 文件编译流程详解与 Babel 的使用 上文分析了 vue3 的编译过程&#xff0c;但是在对其中样式的解析遗留了一…...

如何获取钉钉webhook

第一步打开钉钉并登录 第二步创建团队 并且 添加自定义 机器人 即可获取webhook...

网页WebRTC电话和软电话哪个好用?

关于WebRTC电话与软件电话哪个更好用&#xff0c;这实际上取决于多个因素&#xff0c;并没有一个绝对的答案。不过&#xff0c;我可以根据WebRTC技术的一些特点&#xff0c;以及与传统软件电话相比的优劣势&#xff0c;为你提供一个清晰的对比。 首先&#xff0c;让我们了解一下…...

MySQL Mail服务器集成:如何配置发送邮件?

MySQL Mail插件使用指南&#xff1f;怎么优化 MySQL发邮件性能&#xff1f; MySQL Mail服务器的集成&#xff0c;使得数据库可以直接触发邮件发送&#xff0c;极大地简化了应用架构。AokSend将详细介绍如何配置MySQL Mail服务器&#xff0c;以实现邮件发送功能。 MySQL Mail&…...

【Rockchip系列】官方函数:imcopy

imcopy 函数原型 IM_STATUS imcopy(const rga_buffer_t src,rga_buffer_t dst,int sync 1,int *release_fence_fd NULL);功能说明 imcopy函数用于执行单次快速图像拷贝操作&#xff0c;将图像从源缓冲区拷贝到目标缓冲区。 参数说明 参数描述src[必填] 源图像缓冲区&…...

Matlab实现鲸鱼优化算法优化回声状态网络模型 (WOA-ESN)(附源码)

目录 1.内容介绍 2部分代码 3.实验结果 4.内容获取 1内容介绍 鲸鱼优化算法&#xff08;Whale Optimization Algorithm, WOA&#xff09;是一种基于座头鲸捕食行为的群智能优化算法。该算法通过模仿座头鲸使用螺旋形路径和包围猎物的策略来探索和开发解空间&#xff0c;以找到…...

迈威通信闪耀工博会,以创新科技赋能工业自动化

昨日&#xff0c;在圆满落幕的第24届中国国际工业博览会上&#xff0c;迈威通信作为工业自动化与智慧化领域的先行者&#xff0c;以“创新打造新质通信&#xff0c;赋能工业数字化”为主题精彩亮相&#xff0c;向全球业界展示了我们在工业自动化领域的最新成果与创新技术。此次…...

C# DotNetty客户端

1. 引入DotNetty包 我用的开发工具是VS2022&#xff0c;不同工具引入可能会有差异 工具——>NuGet包管理器——>管理解决方案的NuGet程序包 搜索DotNetty 2.新建EchoClientHandler.cs类 用于接收服务器返回数据 public class EchoClientHandler : SimpleChannelIn…...

4G模组SIM卡电路很简单,但也要注意这些坑

上次水SIM卡相关的文章&#xff0c;还是上一次&#xff1b; 上一篇文章里吹牛说&#xff0c;跟SIM卡相关的问题还有很多&#xff0c;目的是为下一篇文章埋下伏笔&#xff1b;伏笔埋是埋下了&#xff0c;但如果债老是不还&#xff0c;心里的石头就总悬着&#xff0c;搞不好老板…...

常见电脑品牌BIOS设置与进入启动项快捷键

常见电脑品牌BIOS与引导项快捷键速查表 | 电脑品牌 | BIOS快捷键 | 引导项快捷键 | 备注 ||------------|------------|--------------|------------------------------ || 联想 | F2/F1 | F12 | 笔记本通常为F2&#xff0c;台式机通常为F1 || IBM/ThinkPad | F1 | 未知 | ||…...

C语言中的日志机制:打造全面强大的日志系统

前言 在软件开发中&#xff0c;良好的日志记录机制对于调试、监控程序状态和维护系统的稳定性至关重要。本文将介绍如何在C语言中构建一个全面强大的日志系统&#xff0c;并提供一些示例代码。 1. 日志的基本概念 日志级别&#xff1a;用于分类日志信息的重要性&#xff0c;…...

局域网广域网,IP地址和端口号,TCP/IP 4层协议,协议的封装和分用

前言 在古老的年代&#xff0c;如果我们要实现两台机器进行数据传输&#xff0c; A员工就得去B员工的办公电脑传数据&#xff08;B休息&#xff0c;等A传完&#xff09;&#xff0c;这样就很浪费时间 所以能不能不去B的工位的同时&#xff0c;还能传数据。这时候网络通信就出来…...

LabVIEW项目编码器选择

在LabVIEW项目中&#xff0c;选择增量式&#xff08;Incremental Encoder&#xff09;和绝对式&#xff08;Absolute Encoder&#xff09;编码器取决于项目的具体需求。增量式编码器和绝对式编码器在工作原理、应用场景、精度和成本等方面存在显著差异。以下从多方面详细阐述两…...

Spring Boot实现房产租赁业务逻辑

1 绪论 1.1 研究背景 中国的科技的不断进步&#xff0c;计算机发展也慢慢的越来越成熟&#xff0c;人们对计算机也是越来越更加的依赖&#xff0c;科研、教育慢慢用于计算机进行管理。从第一台计算机的产生&#xff0c;到现在计算机已经发展到我们无法想象。给我们的生活改变很…...

汽车3d动画渲染选择哪个?选择最佳云渲染解决方案

面临汽车3D动画渲染挑战&#xff1f;选择正确的云渲染服务至关重要。探索最佳解决方案&#xff0c;优化渲染效率&#xff0c;快速呈现逼真动画。 汽车3d动画渲染选择哪个&#xff1f; 对于汽车3D动画渲染&#xff0c;选择哪个渲染器取决于你的项目需求、预算和期望的效果。Ble…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

质量体系的重要

质量体系是为确保产品、服务或过程质量满足规定要求&#xff0c;由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面&#xff1a; &#x1f3db;️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限&#xff0c;形成层级清晰的管理网络&#xf…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下&#xff0c;企业和个人创作者为了扩大影响力、提升传播效果&#xff0c;纷纷采用短视频矩阵运营策略&#xff0c;同时管理多个平台、多个账号的内容发布。然而&#xff0c;频繁的文案创作需求让运营者疲于应对&#xff0c;如何高效产出高质量文案成…...

【WebSocket】SpringBoot项目中使用WebSocket

1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖&#xff0c;添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...