Spring 之 @Import 注解使用与源码浅析

news2025/3/1 1:26:56

1、@Import 的作用?

再说 @Import 之前先回忆下 @Component 的作用,在类上标注该注解,该类就能够被 Spring 扫描封装成 BeanDefinition 并注册到容器中。但现在需要将第三方 jar 包、或者其他路径下面的包中的类也要被扫描注册呢?使用 @Component 处理并不是很友好(加一个 jar 包你都要指定扫描路径太费劲了),所以就有了 @Import,它就可以完美的支持第三方类库的导入,SpringBoot 中就非常喜欢用这种方式,提前把所有的配置类写好组装到一个模块,需要用到的地方直接通过 @Import 导入即可。

2、@Import 四个基本用法

2.1、直接导入实体类

直接导入就是将需要导入的 Apple 类直接写死,如下:


public class Apple {

}

@Import(Apple.class)
public class AtImportTest {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AtImportTest.class);

		Apple bean = context.getBean(Apple.class);
		System.out.println("bean = " + bean);
	}
}

虽然这种方式最简单,但是有一个弊端,就是当你需要导入很多类的时候,就需要一个一个这样指定太浪费时间了,所以就衍生出下面的几种方式。

2.2、实现 ImportSelector 接口

注意 AppleImportSelector 类上面不要加 @Component 注解,被 @ComponentScan 扫描的话是不会回调到 selectImports() 方法的,这样就不可能导入 Apple 类。


public class Apple {

}

public class AppleImportSelector implements ImportSelector {

	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		return new String[]{Apple.class.getName()};
	}
}

@Import(AppleImportSelector.class)
public class AtImportTest {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AtImportTest.class);

		Object bean = context.getBean(Apple.class);
		System.out.println("bean = " + bean);
	}
}

2.3、实现 DeferredImportSelector 接口

这个接口其实是 ImportSelector 的扩展接口,实现了这个接口导入的类,表示这些导入的类是需要延迟装载的。SpringBoot 自动装配中就用这个类来延迟加载 spring.factories 中配置类,毕竟自动装配是带有 n 多条件的,只有符合某些条件才能够装载的。具体用法如下:

可以实现 DeferredImportSelector 接口,并实现 Group 接口,一般这个 Group 的实现定义为静态嵌套类比较好点,加载和外部类会一起加载。


public class Apple {

}

public class AppleDeferredImportSelector implements DeferredImportSelector {

	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {

		return null;
	}

	@Override
	public Class<? extends Group> getImportGroup() {
		return MyInnerGroup.class;
	}

	private static class MyInnerGroup implements Group {

		List<Entry> list = new ArrayList<>();
		AnnotationMetadata metadata = null;

		@Override
		public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
			this.metadata = metadata;
			Entry entry = new Entry(metadata,Apple.class.getName());
			list.add(entry);
		}

		@Override
		public Iterable<Entry> selectImports() {

			return list;
		}
	}
}


@Import(AppleDeferredImportSelector.class)
public class AtImportTest {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AtImportTest.class);

		Apple bean = context.getBean(Apple.class);
		System.out.println("bean = " + bean);

	}
}

这里需要注意,你实现 Group 接口,其实人家也有一个默认 DeferredImportSelectorGrouping 的组。只不过此时你要实现好 AppleDeferredImportSelector 类的 selectImports() 方法。

2.4、实现 ImportBeanDefinitionRegistrar 接口

直接实现 ImportBeanDefinitionRegistrar 接口,这个接口就更方便了,直接可以让你自己注册 BeanDefinition,代码如下:


public class Apple {

}

public class AppleImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		AbstractBeanDefinition genericBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		genericBeanDefinition.setBeanClass(Apple.class);

		registry.registerBeanDefinition("apple",genericBeanDefinition);
	}
}


@Import(AppleImportBeanDefinitionRegistrar.class)
public class AtImportTest {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AtImportTest.class);

		Object bean = context.getBean(Apple.class);
		System.out.println("bean = " + bean);

	}
}

但是这里需要注意一个小问题,ImportBeanDefinitionRegistrar 提供了两个方法,如果你将两个方法都重写的话只会生效三个参数的方法,两个参数的方法将不会被调到,除非你手动去调用,后面源码会分析到。

3、源码分析

@Import 最终就是要将类能够加载到 Spring 并封装成 BeanDefinition 注册到容器使命就算告终,现在就开始类分析下 @Import 是怎么将类加载到 Spring 的,那么还是得从老朋友进军 ConfigurationClassPostProcessor(这个类化成骨灰都要认得)。

入口源码如下:

在这里插入图片描述

先看到入参 getImports() ,该方法主要是去收集 @Import 导入的类,源码如下:

在这里插入图片描述

在这里插入图片描述

然后再进入 processImports() 方法内部逻辑,其实 @Import 的基本所有逻辑都在这个方法里面,但是回调方法不是在下面这段代码哦,下面这段代码更多的还是去做收集动作,源码如下:

在这里插入图片描述

解释一下上面这段代码吧,遍历收集完的类,遍历每个类有设么特征,然后执行什么操作?

如果实现了 ImportSelector 接口的立马就去回调该接口方法 selectImports() (可以看见这个接口的执行时序还是非常高的);因为这个执行太及时了,所以在 SpringBoot 自动装配中就不会使用这个类作为导入类。

如果实现了 DeferredImportSelector 接口的,并没有立刻去回调该接口的方法,而是将导入的类保存到了 deferredImportSelectors List 集合中,这里收集保存,那么肯定是准备给后面哪个地方使用。所以 Spring 中就是使用的这个类作为自动装配的导入类,源码如下:

在这里插入图片描述

如果实现了 ImportBeanDefinitionRegistrar 接口的话,也是将导入的类保存到一个 importBeanDefinitionRegistrars Map 集合中,不用想,肯定又是给后面做铺垫,SpringBoot 自动装配类太多,所以也不可能用这个来一个一个手动注入,源码如下:

在这里插入图片描述

如果上述接口都没有实现,那么就会直接走最后的 else 阶段递归调用 processConfigurationClass() 方法,因为谁也不知道你这个类或者类里面有没有其他注解。如果灭有其他注解的话,最终被保存到 configurationClasses Map 集合中。

这里补充一个 ConfigurationClass 类,这个类是个包装类,里面包装了很多的东西,源码如下:

在这里插入图片描述

ImportedBy 集合:主要用来存什么?这样解释下,比如 @Import(Apple.class) public AtImportTest{ } 那么这个集合保存的就是 AtImportTest 类,因为 Apple 是被 AtImportTest 类导入的,或者说一个外部类和内部类的关系,内部类肯定都是有外部类导入的,所以 ImportedBy 保存的就是外部类

beanMethods 集合:主要保存 @Bean 修饰的方法,也只能是方法

importedResources 集合:主要用来保存 @ImportResource 导入的资源类

importBeanDefinitionRegistrars 集合:主要用来保存实现了 ImportBeanDefinitionRegistrar 接口的类

然后 ConfigurationClassParser 类中有个集合,如下:

在这里插入图片描述

configurationClasses 集合:主要用来保存被解析完的类,因为 @Bean、@Import 这些类还没有注册到容器中,暂时保存到 configurationClasses 集合中。

deferredImportSelectorHandler 对象:保存实现了 DeferredImportSelector 接口的类,它里面有个集合 deferredImportSelectors 保存这个类。

补充完之后,继续回到正题,从上面的一序列介绍,都只是讲 @Import 导入的类暂存到 configurationClasses Map 集合中,那么到底什么时候被注册到 Spring 容器中,源码如下:

在这里插入图片描述

从上面源码可以看出,调用实在 parse() 解析完成之后才调的。要知道这个时候其实很多 bean 已经被扫描加载到了 Spring 容器中,进入内部逻辑如下:

在这里插入图片描述

可以看到没有实现任何接口通过 @Import 导入进来的类在第一行就被注册到 Spring 容器中,被 @Bean 修饰的方法在第二行被执行。而实现了 ImportBeanDefinitionRegistrar 接口的类,在最后一行执行,进入源码如下:

在这里插入图片描述

注意这个 registerars 就是前面提到的 importBeanDefinitionRegistrars 集合,保存实现了 ImportBeanDefinitionRegistrar 接口的类。

发现没,这里进来就默认调用 ImportBeanDefinitionRegistrar 接口中有三个参数的方法,然后继续进入内部如下:

在这里插入图片描述

发现自己会手动调用两个参数的方法,所以这里就是为什么上面提到,如果你重写了 ImportBeanDefinitionRegistrar 接口的两个方法的话,就只会回调到三个参数的方法,因为我们重写了 registerBeanDefinitions(三个参数) 方法。

现在还有一个 deferredImportSelectors 集合中保存的东西,在哪里被使用呢?源码如下:

在这里插入图片描述

deferredImportSelectors 集合中就是保存的就是实现了 DeferredImportSelector 接口的类,表示这些类需要被延迟加载,从上面源码可以看出,是在 parse() 完之后执行的,除了需要延迟加载的类,所有的类都已经加载完成。所以为什么 SpringBoot 自动装配中需要将 spring.factories 中的配置通过延迟加载的方式加入到 Spring 容器中,因为自动装配是有 @Condition 条件判断的(比如:@ConditionalOnMissingBean 需要容器中没有这个 bean 才可以注册该 bean),所以自动装配采取的是延迟执行,并且还可以进行分组,分组之后每组成员加载顺序是互补影响的。

进入 process() 核心源码如下:

在这里插入图片描述

获取到实现 DeferredImportSelector 接口的所有类,然后挨个调用 register() 进行注册,看到注册两个字眼,肯定又是把这个元素保存到了某个容器中,然后后面肯定需要用到。然后再调用 processGroupImports() 方法,回调到 DeferredImportSelector 接口方法。源码如下:

在这里插入图片描述

这里有两个地方要注意,groupings 的值从哪里来的呢?configurationClasses 的值又是从哪里来的?

首先看到 groupings 是怎么来的,那么就要回到上面的 register() 注册过程了,源码如下:

在这里插入图片描述

发现 groupings 的值有两种情况:第一种:如果 group 不为 null,那么就使用这个 group 作为值,那么怎么才回让 group 不为 null ?

源码是直接返回 null 的,只要我们能够覆写 getImportGroup() 方法就可以让 group 有不为 null,如下:

在这里插入图片描述

重写之后的如下:

public class AppleDeferredImportSelector implements DeferredImportSelector {

	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {

		return new String[]{Apple.class.getName()};
	}

	@Override
	public Class<? extends Group> getImportGroup() {
		return MyInnerGroup.class;
	}

	private static class MyInnerGroup implements Group {

		List<Entry> list = new ArrayList<>();
		AnnotationMetadata metadata = null;

		@Override
		public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
			this.metadata = metadata;
			Entry entry = new Entry(metadata,Apple.class.getName());
			list.add(entry);
		}

		@Override
		public Iterable<Entry> selectImports() {
			
			return list;
		}
	}
}

process() 方法主要用来去收集处理导入进来的类,selectImports() 就是一个结果返回。group 有值了,那么 groupings 就相当于有值了,configurationClasses 从上面源码中也可以看到是保存的触发导入的类。

然后接着继续往下分析,源码如下:

在这里插入图片描述

看到下面这段源码,最终会回调到每个实现了 Group 接口的 process() 和 selectImports() 方法。deferredImports 容器装的是实现了 DeferredImportSelector 接口的类。

在这里插入图片描述

至此对于 @Import 导入类的加载并注册到容器完成。注意此时注册完成说的都是针对 BeanDefinition,并不是真正的实例。

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

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

相关文章

Unity 制作一个简单的星系

使用素材&#xff1a; 1.Planets with Space Background in Flat Style 2.Planet Icons 创建场景 编写脚本 using System.Collections; using System.Collections.Generic; using UnityEngine;public class Cytaster : MonoBehaviour {[SerializeField]private float rotate_s…

【LeetCode】矩阵置零 [M](矩阵)

73. 矩阵置零 - 力扣&#xff08;LeetCode&#xff09; 一、题目 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&a…

uni-app - 封装全局 API 调用弹框组件

uni-app - 在纯 JS 文件中调用自定义弹框组件 / 封装全局 API 调用弹框组件&#xff08;解决小程序、APP 无法使用 document.body.appendChild 插入组件节点&#xff09;适配全端 uni-app中实现一个全局弹层组件 引用超级全局组件方案 一、安装 npm install vue-inset-loade…

零入门容器云网络-9:命令行式操作tun设备介绍

已发表的技术专栏&#xff08;订阅即可观看所有专栏&#xff09; 0  grpc-go、protobuf、multus-cni 技术专栏 总入口 1  grpc-go 源码剖析与实战  文章目录 2  Protobuf介绍与实战 图文专栏  文章目录 3  multus-cni   文章目录(k8s多网络实现方案) 4  gr…

测开工具:spring boot 实现同步数据库表结构

源码&#xff1a; GitHub - 18713341733/mysqlsync 一、使用场景 一个项目&#xff0c;有多套开发环境。有一套标准的数据库&#xff0c;不同的开发环境&#xff0c;有各自的一套数据库。 标准数据库的表结构经常发生变化&#xff0c;不同的开发环境中的数据库&#xff0c;…

C#,图像二值化(04)——全局阈值 Kittler 算法及其源程序

1、Kittler算法&#xff08;最小误差法&#xff09;概述 最小误差法是 J. Kittler & J. Illingworth 1986年在《MINIMUM ERROR THRESHOLDING》文章中提出的一种基于直方图的阈值分割方法,简称 Kittler 算法。其思想:假设灰度图像由目标和背景组成,且目标和背景满足一混合高…

11个技巧让你成为更好的 Typescript 程序员

学习 Typescript 通常是一次重新发现之旅。您的最初印象可能非常具有欺骗性&#xff1a;这不就是一种注释 Javascript 的方式&#xff0c;所以编译器可以帮助我找到潜在的错误吗&#xff1f; 通过 r/mevlixreddit 虽然这句话通常是正确的&#xff0c;但随着您继续前进&#xff…

【聆思CSK6 视觉AI开发套件试用】AI识别试用以及闭坑方案

本篇文章来自极术社区与聆思科技组织的CSK6 视觉AI开发套件活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;X Y Z 非常感谢能有这次机会体验聆思CSK6 视觉AI开发套件。上班的一大早收到了快递&#xff0c;迫不及待的打开快递。必须先来个图&#xff0…

4个技巧,节约网络工程师一半的时间

01 批量ping网段 对于一个网段ip地址众多&#xff0c;如果单个检测实在麻烦&#xff0c;那么你就可以直接批量ping网段检测&#xff0c;那个ip地址出了问题&#xff0c;一目了然。 先看代码&#xff0c;直接在命令行窗口输入&#xff1a; for /L %D in (1,1,255) do ping 10…

渗透测试神器--Burp Suite

一、介绍 Burp Suite 是用于攻击web 应用程序的集成平台。Burp Suite是一款信息安全从业人员必备的集成型的渗透测试工具&#xff0c;它采用自动测试和半自动测试的方式&#xff0c;包含了Proxy、Spider、Scanner、Intruder、Repeater、Sequencer、Decoder、Comparer等工具模块…

uniapp 窗口小工具、桌面小部件、微件 Ba-AppWidget

简介&#xff08;下载地址&#xff09; Ba-AppWidget 是一款窗口小工具&#xff08;桌面小部件、微件&#xff09;插件&#xff0c;默认为音乐播放器的样式&#xff0c;有其他界面需要&#xff0c;可联系作者定制。 支持点击事件监听支持动态更改页面内容支持设置小工具的预览…

区块链(一): 以太坊基础知识

目录什么是区块链&#xff1f;什么是以太坊&#xff1f;什么是加密货币&#xff1f;以太坊与比特币有什么不同&#xff1f;以太坊能做什么&#xff1f;什么是智能合约&#xff1f;以太坊社区以太坊白皮书什么是区块链&#xff1f; 区块链是一个交易数据库&#xff0c;在网络中…

容器,容器技术,云容器相关入门知识

前言 最近面试了一家国企&#xff0c;交谈愉快&#xff0c;对方的工程师问到容器时&#xff0c;突然愣了一下。脑子里有学习前端时候学习的docker&#xff0c;但印象里docker可不能代表容器技术&#xff0c;于是学习容器相关知识后整理相关知识以作巩固。 什么是容器 有点开…

SuperMap iDesktop/iDesktopX 端性能优化

作者&#xff1a;yd&hyy 一、背景 在使用iDesktop/iDesktopX的三维场景加载GIS数据的过程中&#xff0c;随着数据的种类、大小、数量的增多&#xff0c;往往会有很多的性能问题&#xff0c;加载速率缓慢&#xff0c;数据显示清晰度不足&#xff0c;多数据交叠显示错误&am…

『分分钟玩转VueRouter●上』VueRouter的一些基础配置

文章目录前言一、vue中如何使用VueRouter?二、路由使用的基本配置1.多级路由配置2.路由中的query参数3.命名路由4.路由的params参数5.路由的props配置6.router-link的replace属性7.通配符路由前言 计算机网络中有一个路由的概念&#xff1a;路由是指网络数据包发送到目的地址的…

php宝塔搭建部署实战SDCMS蓝色通用宽屏企业网站源码

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 本期给大家带来一套php开发的SDCMS蓝色通用宽屏企业网站源码&#xff0c;感兴趣的朋友可以自行下载学习。 技术架构 PHP7.2 nginx mysql5.7 JS CSS HTMLcnetos7以上 宝塔面板 文字搭建教程 下载源码&…

maven 继承和聚合的区别

maven 继承和聚合的区别 参考 https://cloud.tencent.com/developer/article/1397748 继承 目的&#xff1a;统一管理version版本&#xff0c;少写冗余代码。使用&#xff1a; 父类pom不写业务&#xff0c;只写 pom的jar包版本等信息&#xff0c;子类中使用 parent 标签&…

STM32G473CBT6关于ADC采集的总结

STM32G473CBT6单片机在浮点运算&#xff0c;信号采集、数据处理方面有很大的用途。因相关的资料较少&#xff0c;特此做一下笔记&#xff0c;方便后期使用。STM32CubeMX软件比较强大&#xff0c;兼容IAR和keil方便直接生成代码文件&#xff0c;但相关的库不熟悉&#xff0c;好东…

【Web安全】应用层拒绝服务攻击

目录 1、DDOS简介 &#xff12;、应用层DDOS 2.1 &#xff23;&#xff23;攻击 2.2 限制请求频率 2.3 道高一尺&#xff0c;魔高一丈 3、验证码 &#xff23;&#xff21;&#xff30;&#xff34;&#xff23;&#xff28;&#xff21; 4、防御应用层DDOS 5、资源…

章节五:RASA NLU组件介绍--语言模型和分词器

​ 这里写目录标题一、前言二、语言模型组件1、MitieNLP2、SpacyNLP三、分词器1、WhitespaceTokenizer2、JiebaTokenizer3、MitieTokenizer4、SpacyTokenizer5、自定义分词器一、前言 RASA在处理对话时&#xff0c;整体流程是pipeline结构&#xff0c;自然语言理解&#xff08…