Spring之依赖注入源码解析

news2024/11/11 6:17:55

Spring之依赖注入源码解析

依赖注入原理流程图:

https://www.processon.com/view/link/5f899fa5f346fb06e1d8f570

Spring 中有几种依赖注入的方式?

首先分为两种:

1、手动注入

2、自动注入

1、手动注入

在 XML 中定义 Bean 时,就是手动注入,因为是程序员手动给某个属性指定了值。

下面这种底层是通过 set 方法进行注入:

<bean name="orderService" class="com.luban.service.orderService" />

<bean name="userService" class="com.luban.service.UserService">
	<property name="orderService" ref="orderService"/>
</bean>

下面这种底层是通过构造方法进行注入:

<bean name="orderService" class="com.luban.service.orderService" />

<bean name="userService" class="com.luban.service.UserService">
	<constructor-arg index="0" ref="orderService"/>
</bean>

所以手动注入的底层也分为两种:

1、set方法注入

2、构造方法注入

2、自动注入

自动注入又分为两种:

1、XML的 autowire 自动注入

2、@Autowired注解的自动注入

2.1、XML的autowire自动注入

在 XML 中,我们可以在定义一个 Bean 时指定这个 Bean 的自动注入模式:

1、byType

2、byName

3、constructor

4、default

5、no

比如:

<bean id="userService" class="com.luban.service.UserService" autowire="byType"/>

这么写,表示 Spring 会自动给 userService 中的所有属性进行自动赋值(不需要这个属性上有 @Autowired 注解,但需要这个属性有对应的 set 方法)。

在创建 Bean 的过程中,在填充属性时,Spring 会去解析当前类,把当前类的所有方法都解析出来,Spring 会去解析每个方法得到对应的 PropertyDescriptor 对象,PropertyDescriptor 中有几个属性:

1、name:这个 name 并不是方法的名字,而是拿方法名字进行处理后的名字

  • 如果方法名字以“get”开头,比如“getXXX”,那么name=XXX
  • 如果方法名字以“is”开头,比如“isXXX”,那么name=XXX
  • 如果方法名字以“set”开头,比如“setXXX”,那么name=XXX

2、readMethodRef:表示 get 方法的 Method 对象的引用

3、readMethodName:表示 get 方法的名字

4、writeMethodRef:表示 set 方法的 Method 对象的引用

5、writeMethodName:表示 set 方法的名字

6、propertyTypeRef:如果是 get 方法那么对应的就是返回值类型,如果是 set 方法那么对应的就是 set 方法中唯一参数的类型

get 方法的定义是: 方法参数个数为 0 个,并且 (方法名字以"get"开头 或者 方法名字以"is"开头且方法的返回值类型为 boolean

set 方法的定义是:方法参数个数为 1 个,并且 (方法名字以"set"开头且方法的返回值类型为 void

Untitled

Untitled

Untitled

Spring 在通过 byName 自动填充属性时的流程是:

1、找到所有 set 方法所对应的 XXX 部分的名字

2、根据 XXX 部分的名字去获取 bean

Untitled

Spring 在通过 byType 自动填充属性时的流程是:

1、获取到 set 方法中的唯一参数的参数类型,并根据该类型去容器中获取对应的 Bean

2、如果找到多个,则会报错

分析了 autowire 的 byTypebyName 的情况,接下来分析 constructor,constructor表示通过构造方法进行注入,这种情况就比较简单了,没有 byType 和 byName 那么复杂。

如果是 constructor,那么就可以不写 set 方法了,当某个 bean 是通过构造方法进行注入时,Spring 会利用构造方法的参数信息从 Spring 容器中去找 Bean,找到 Bean 之后作为参数传给构造方法,从而实例化得到一个 Bean 对象,并完成属性赋值(属性赋值的代码得由程序员来写)。

这里先不考虑一个类有多个构造方法的情况,后面单独讲推断构造方法,我们这里只考虑只有一个有参构造方法。

构造方法注入相当于 byType + byName,普通的 byType 是根据 set 方法中的参数类型去找 bean,找到多个则会报错。而 constructor 就是通过构造方法中的参数类型去找 bean,如果找到多个则会根据参数名确定。

另外两个:

1、no,表示关闭 autowire

2、default,表示默认值,我们一直演示某个 bean 的 autowire,其实也可以直接在 <beans> 标签中设置 autowire,如果设置了,那么 <bean> 标签中设置的 autowire 如果为 default,则会用 <beans> 标签中设置的 autowire。

可以发现 XML 中的自动注入还是挺强大的,那么问题来了,为什么我们平时都是用 @Autowired 注解呢?而没有用上文说的这种自动注入方式呢?

@Autowired 注解相当于 XML 中的 autowire 属性的替代。从本质上讲,@Autowired 注解提供了与 autowire 相同的功能,但却拥有更细粒度的控制和更广泛的适用性。

注意:更细粒度的控制。

XML 中的 autowire 控制的是整个 bean 的所有属性,而 @Autowired 注解是直接写在某个属性、某个 set 方法、某个构造方法上的。

举个例子:一个类有多个构造方法,如果用 XML 的 autowire=constructor,那么你无法控制到底用哪个构造方法,而你可以用 @Autowired 注解来指定你想用哪个构造方法。

同时,用 @Autowired 注解还可以控制哪些属性想被自动注入,哪些属性不想,这也是细粒度的控制。

但是 @Autowired 无法区分 byType 和 byName,@Autowired 是先进行 byType,如果找到多个则再进行 byName。

XML 的自动注入底层也就是:

1、set 方法注入

2、构造方法注入

2.2、@Autowired注解的自动注入

@Autowired 注解可以写在:

1、属性上:先根据属性类型去找Bean,如果找到多个再根据属性名确定一个

2、构造方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个

3、set方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个

@Autowired 注解的自动注入底层也就是:

1、属性注入

2、构造方法注入

3、set方法注入

3、寻找注入点

在创建一个 Bean 的过程中,Spring 会利用 AutowiredAnnotationBeanPostProcessor 的 postProcessMergedBeanDefinition() 找到注入点并缓存,找注入点的流程为:

Untitled

Untitled

Untitled

1、遍历当前类中的所有属性字段 Field

2、查看属性字段上是否存在 @Autowired@Value@Inject 中的其中一个,如果存在则认为该字段是一个注入点

3、如果字段是 static 的,则不进行自动注入

Untitled

4、获取 @Autowired 中的 required 属性的值

Untitled

5、将字段信息构造成一个 AutowiredFieldElement 对象,作为一个注入点对象添加到 currElements 集合中。

6、遍历当前类中的所有方法 Method

7、判断当前方法是否是桥接方法,如果是则找到原方法

8、查看方法上是否存在 @Autowired@Value@Inject 中的其中一个,如果存在则认为该方法是一个注入点

9、如果方法是static的,则不进行自动注入

Untitled

10、获取 @Autowired 注解中的 required 属性的值

Untitled

11、将方法信息构造成一个 AutowiredMethodElement 对象,作为一个注入点对象添加到 currElements 集合中。

12、遍历完当前类的属性和方法后,将继续遍历其父类的属性和方法,直到没有父类。

13、最后将 currElements 集合封装成一个 InjectionMetadata 对象,作为当前 Bean 对应的注入点集合对象,并缓存。

Untitled

3.1、static的字段和方法为什么不能作为注入点?

举个例子:

@Component
@Scope("prototype")
public class OrderService {

}
@Component
@Scope("prototype")
public class UserService  {

 @Autowired
 private static OrderService orderService;

 public void test() {
	  System.out.println(orderService);
 }

}

看上面的代码,UserService 和 OrderService 都是原型 Bean,假设 Spring 支持 static 字段进行自动注入,那么现在调用两次

1. UserService userService1 = context.getBean("userService")
2. UserService userService2 = context.getBean("userService")

此时,userService1 的 orderService 值是什么?还是它自己注入的值吗?

不是,一旦 userService2 创建好之后,static OrderService 字段的值就发生了修改,从而出现了 bug。

3.2、桥接方法

4、注入点进行注入

Spring 在 AutowiredAnnotationBeanPostProcessor 的 postProcessProperties() 方法中,会遍历所有找到的注入点并依次进行注入。

Untitled

属性注入

Untitled

Untitled

1、遍历所有的 AutowiredFieldElement 对象。

2、将对应的字段封装成 DependencyDescriptor 对象。

Untitled

3、调用 BeanFactory 的 resolveDependency() 方法,传入 DependencyDescriptor 对象,进行依赖查找,找到当前字段所匹配的 Bean 对象。

Untitled

4、将 DependencyDescriptor 对象和所找到的 Bean 对象的名字(BeanName)封装成一个 ShortcutDependencyDescriptor 对象作为缓存,比如当前 Bean是原型 Bean,那么下次再来创建该 Bean 时,就可以直接拿缓存中的 Bean 对象的名字(BeanName)去 BeanFactory 中获取 Bean 对象了,不用再次进行查找了。

Untitled

5、利用反射将获取到的Bean对象赋值给字段。

Set方法注入

Untitled

Untitled

1、遍历所有的 AutowiredMethodElement 对象。

2、遍历对应的方法参数,将每个参数封装成 MethodParameter 对象。

3、将 MethodParameter 对象封装成 DependencyDescriptor 对象。

Untitled

4、调用 BeanFactory 的 resolveDependency() 方法,传入 DependencyDescriptor 对象,进行依赖查找,找到当前方法参数所匹配的 Bean 对象。

Untitled

5、将 DependencyDescriptor 对象和所找到的 Bean 对象的名字(BeanName)封装成一个 ShortcutDependencyDescriptor 对象作为缓存,比如当前 Bean 是原型 Bean,那么下次再来创建该 Bean 时,就可以直接拿缓存中的 Bean 对象的名字(BeanName)去 BeanFactory 中获取 Bean 对象了,不用再次进行查找了。

Untitled

6、利用反射将找到的所有结果对象传给当前方法,并执行。

5、核心方法详解

5.1、resolveDependency( )实现

Untitled

Untitled

	/**
	 * 点进去看看 DefaultListableBeanFactory
	 */
	@Nullable
	Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException;

该方法表示,传入一个依赖描述(DependencyDescriptor),该方法会根据该依赖描述从 BeanFactory 中找出对应的唯一的一个 Bean 对象。

DefaultListableBeanFactory 中的 resolveDependency() 方法的具体实现:

https://www.processon.com/view/link/5f8d3c895653bb06ef076688

5.2、findAutowireCandidates( )实现

根据类型找 BeanName 的底层流程:

https://www.processon.com/view/link/6135bb430e3e7412ecd5d1f2

对应的执行流程图:

https://www.processon.com/view/link/5f8fdfa8e401fd06fd984f20

Untitled

Untitled

Untitled

1、找出 BeanFactory 中类型为 type 的所有 Bean 的名字,注意是名字(BeanName),而不是 Bean 对象,因为我们可以根据 BeanDefinition 就能判断和当前 type 是不是匹配,不用生成 Bean 对象

Untitled

2、把 resolvableDependencies 中 key 为 type 的对象找出来并添加到 result 中

3、遍历根据 type 找出的 beanName,判断当前 beanName 对应的 Bean 是不是能够被自动注入

Untitled

Untitled

4、先判断 beanName 对应的 BeanDefinition 中的 autowireCandidate 属性,如果为 false 表示不能用来进行自动注入,如果为 true 则继续进行判断

Untitled

5、判断当前 type 是不是泛型,如果是泛型则会把容器中所有的 beanName 找出来,如果是这种情况,那么在这一步中就要获取到泛型的真正类型,然后进行匹配,如果当前 beanName 和当前泛型对应的真实类型匹配,那么则继续判断

6、如果当前 DependencyDescriptor 上存在 @Qualifier 注解,那么则要判断当前 beanName 上是否定义了 Qualifier,并且是否和当前DependencyDescriptor 上的 Qualifier 相等,相等则匹配
7、经过上述验证之后,当前 beanName 才能成为一个可注入的,添加到 result 中

6、关于依赖注入中泛型注入的实现

首先在 Java 反射中,有一个 Type 接口,表示类型,具体如下:

  • raw types:也就是普通 Class
  • parameterized types:对应 ParameterizedType 接口,泛型类型
  • array types:对应 GenericArrayType,泛型数组
  • type variables:对应 TypeVariable 接口,表示类型变量,也就是所定义的泛型,比如 T、K
  • primitive types:基本类型,int、boolean

举个例子:

public class TypeTest<T> {

	private int i;
	private Integer it;
	private int[] iarray;
	private List list;
	private List<String> slist;
	private List<T> tlist;
	private T t;
	private T[] tarray;

	public static void main(String[] args) throws NoSuchFieldException {

		test(TypeTest.class.getDeclaredField("i"));
		System.out.println("=======");
		test(TypeTest.class.getDeclaredField("it"));
		System.out.println("=======");
		test(TypeTest.class.getDeclaredField("iarray"));
		System.out.println("=======");
		test(TypeTest.class.getDeclaredField("list"));
		System.out.println("=======");
		test(TypeTest.class.getDeclaredField("slist"));
		System.out.println("=======");
		test(TypeTest.class.getDeclaredField("tlist"));
		System.out.println("=======");
		test(TypeTest.class.getDeclaredField("t"));
		System.out.println("=======");
		test(TypeTest.class.getDeclaredField("tarray"));

	}

	public static void test(Field field) {

		if (field.getType().isPrimitive()) {
			System.out.println(field.getName() + "是基本数据类型");
		} else {
			System.out.println(field.getName() + "不是基本数据类型");
		}

		if (field.getGenericType() instanceof ParameterizedType) {
			System.out.println(field.getName() + "是泛型类型");
		} else {
			System.out.println(field.getName() + "不是泛型类型");
		}

		if (field.getType().isArray()) {
			System.out.println(field.getName() + "是普通数组");
		} else {
			System.out.println(field.getName() + "不是普通数组");
		}

		if (field.getGenericType() instanceof GenericArrayType) {
			System.out.println(field.getName() + "是泛型数组");
		} else {
			System.out.println(field.getName() + "不是泛型数组");
		}

		if (field.getGenericType() instanceof TypeVariable) {
			System.out.println(field.getName() + "是泛型变量");
		} else {
			System.out.println(field.getName() + "不是泛型变量");
		}

	}

}

在 Spring 中,当注入点是一个泛型时,也是会进行处理的,比如:

@Component
public class UserService extends BaseService<OrderService, StockService> {

	public void test() {
		System.out.println(o);
	}

}

public class BaseService<O, S> {

	@Autowired
	protected O o;

	@Autowired
	protected S s;

}

1、Spring 扫描时发现 UserService 是一个 Bean

2、那就取出注入点,也就是 BaseService 中的两个属性 o、s

3、接下来需要按注入点类型进行注入,但是 o 和 s 都是泛型,所以 Spring 需要先确定 o 和 s 的具体类型。

4、因为当前正在创建的是 UserService 的 Bean,所以可以通过 userService.getClass().getGenericSuperclass().getTypeName() 获取到具体的泛型信息,比如:

com.zhouyu.service.BaseService<com.zhouyu.service.OrderService, com.zhouyu.service.StockService> 

5、然后再拿到 UserService 的父类 BaseService 的泛型变量:

for (TypeVariable<? extends Class<?>> typeParameter : userService.getClass().getSuperclass().getTypeParameters()) {   
		System.*out*.println(typeParameter.getName());  
}

6、通过上面两段代码,就能知道,o 对应的具体类型就是 OrderService,s 对应的具体类型就是 StockService

7、然后再调用 oField.getGenericType() 就能知道当前 field 使用的是哪个泛型,就能知道具体的类型了

7、@Qualifier注解的使用

8、@Resource注解详解

org.springframework.context.annotation.CommonAnnotationBeanPostProcessor

@Resource 注解底层工作流程图:

https://www.processon.com/view/link/5f91275f07912906db381f6e

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

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

相关文章

Gartner 再度预测2023低代码趋势,真的会赚钱吗?

2023年&#xff0c;从业者对低代码的发展充满了想象&#xff0c;人们认为&#xff0c;未来低代码的商业价值不可估量。 此话并非空穴来风。据Gartner的最新报告显示&#xff0c;到2023年&#xff0c;超过70%的企业将采用低代码作为他们发展战略的关键目标之一&#xff1b;到202…

训练自己的中文word2vec(词向量)--skip-gram方法

训练自己的中文word2vec&#xff08;词向量&#xff09;–skip-gram方法 什么是词向量 ​ 将单词映射/嵌入&#xff08;Embedding&#xff09;到一个新的空间&#xff0c;形成词向量&#xff0c;以此来表示词的语义信息&#xff0c;在这个新的空间中&#xff0c;语义相同的单…

双塔多目标MVKE

MVKE&#xff1a;Mixture of Virtual-Kernel Experts for Multi-Objective User Profile Modeling MVKE论文中是给用户打tag标记&#xff0c;构建用户画像。使用的也是经典的双塔模型&#xff0c;另外在双塔的基础上面叠加了ctr和cvr的多个目标。但是论文最大的创新点是在用户…

基于龙芯 CPU 的气井控制器的软件设计(三)

4.1 系统软件的总体设计 基于龙芯 CPU 的气井控制器的设计需要开发测试硬件模块的测试软件&#xff0c;主要对 RTC 模块、存储器模块、4G 通信、以太网通信、UART 串口以及 AI 模块进行了驱动程序和 应用程序设计。将各个模块设计为不同的任务&#xff0c;龙芯 RTU 软件设计流程…

Redis 监听过期的key(KeyExpirationEventMessageListener)

目录一、简介二、maven依赖三、编码实现3.1、application.properties3.2、Redis配置类3.3、监听器3.4、服务类3.5、工具类四、测试4.1、测试类4.2、单实例4.3、多实例结语一、简介 本文今天主要是讲Redis中对过期key的监听&#xff0c;可能很多小伙伴不会&#xff0c;或者使用会…

day15_常用类

今日内容 上课同步视频:CuteN饕餮的个人空间_哔哩哔哩_bilibili 同步笔记沐沐霸的博客_CSDN博客-Java2301 零、 复习昨日 一、作业 二、代码块[了解] 三、API 四、Object 五、包装类 六、数学和随机 零、 复习昨日 抽象接口修饰符abstractinterface是不是类类接口属性正常属性没…

Leetcode(每日一题)——1139. 最大的以 1 为边界的正方形

摘要 1139. 最大的以 1 为边界的正方形 一、以1为边界的最大正方形 1.1 动态规划 第530题需要正方形所有网格中的数字都是1&#xff0c;只要搞懂动态规划的原理&#xff0c;代码就非常简洁。而这题只要正方形4条边的网格都是1即可&#xff0c;中间是什么数字不用管。 这题…

Hive的安装与配置

一、配置Hadoop环境先看看伪分布式下的集群环境有没有错误的情况&#xff1a;输入命令&#xff1a;start-all.sh jps查看伪分布式的所有进程是否完善二、解压并配置HiveHive压缩包→ https://pan.baidu.com/s/1eOF_ICZV8rV-CEh3nX-7Xw 提取码: m31e 复制这段内容后打开百度网盘…

逆向 xx音乐 aversionid

逆向 xx音乐 aversionid 版本 7.2.0 版本 7.22.0 第一步&#xff0c;charles 抓包 目标字段 aversionid 加固平台 com.stub.StubApp 360加固s.h.e.l.l.S 爱加密com.secneo.apkwrapper.ApplicationWrapper 梆梆加固com.tencent.StubShell.TxAppEntry 腾讯加固 第二步&…

【网络编程】Java快速上手InetAddress类

概念 Java具有较好的网络编程模型/库&#xff0c;其中非常重要的一个API便是InetAddress。在Java.net 网络编程中中有许多类都使用到了InetAddress 这个类代表一个互联网协议&#xff08;IP&#xff09;地址。 IP地址是一个32&#xff08;IPV4&#xff09;位或128&#xff08;…

求职季哪种 Python 程序员能拿高薪?

本文以Python爬虫、数据分析、后端、数据挖掘、全栈开发、运维开发、高级开发工程师、大数据、机器学习、架构师这10个岗位&#xff0c;从拉勾网上爬取了相应的职位信息和任职要求&#xff0c;并通过数据分析可视化&#xff0c;直观地展示了这10个职位的平均薪资和学历、工作经…

02 Context的使用

对于 HTTP 服务而言&#xff0c;超时往往是造成服务不可用、甚至系统瘫痪的罪魁祸首。 context 标准库设计思路 为了防止雪崩&#xff0c;context 标准库的解决思路是&#xff1a;在整个树形逻辑链条中&#xff0c;用上下文控制器 Context&#xff0c;实现每个节点的信息传递…

Package ‘oniguruma‘, required by ‘virtual:world‘, not found

一、操作系统环境 OS版本信息&#xff1a;Rocky Linux 9.1 PHP版本&#xff1a;8.0.26 安装的依赖&#xff1a; dnf -y install libXpm-devel libXext-devel gmp gmp-devel libicu* icu* net-snmp-devel libpng-devel libjpeg-devel freetype-devel libxslt-devel sqlite…

真正意义上的数字零售,最为重要的一点就是要回归零售本身

互联网浪潮的退却并未真正将人们的思维带离互联网的牢笼&#xff0c;相反&#xff0c;越来越多的人依然在用互联网式的眼光看待后互联网时代的事物。尽管这样一种做法可以在一定程度上取得一定的效果&#xff0c;但是&#xff0c;如果仅仅只是用互联网思维来揣度这一切&#xf…

基于虚拟机机的代码保护技术

虚拟机保护技术是基于x86汇编系统的可执行代码转换为字节码指令系统的代码&#xff0c;以达到保护原有指令不被轻易逆向和篡改的目的。 字节码&#xff08;Byte-code&#xff09;是一种包含执行程序&#xff0c;由一序列 op 代码/数据对组成的 &#xff0c;是一种中间码。字节是…

《第一行代码》 第五章:详解广播机制

如果你了解网络通信原理应该会知道&#xff0c;在一个 IP 网络范围中最大的IP 地址是被保留作为广播地址来使用的。比如某个网络的 IP 范围是 192.168.0XXX&#xff0c;子网掩码是255.255.255.0那么这个网络的广播地址就是 192.168.0255广播数据包会被发送到同-网络上的所有端口…

Spring Security OAuth2四种授权模式总结(七)

写在前面&#xff1a;各位看到此博客的小伙伴&#xff0c;如有不对的地方请及时通过私信我或者评论此博客的方式指出&#xff0c;以免误人子弟。多谢&#xff01;如果我的博客对你有帮助&#xff0c;欢迎进行评论✏️✏️、点赞&#x1f44d;&#x1f44d;、收藏⭐️⭐️&#…

【MySQL】MySQL表的增删改查(CRUD)

✨个人主页&#xff1a;bit me&#x1f447; ✨当前专栏&#xff1a;MySQL数据库&#x1f447; ✨算法专栏&#xff1a;算法基础&#x1f447; ✨每日一语&#xff1a;生命久如暗室&#xff0c;不碍朝歌暮诗 目 录&#x1f513;一. CRUD&#x1f512;二. 新增&#xff08;Creat…

将array中元素四舍五入取整的np.rint()方法

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 将array中元素四舍五入取整 np.rint()方法 选择题 关于以下python代码说法错误的一项是? import numpy as np a np.array([-1.7, 1.5, -0.2, 0.3]) print("【显示】a\n",a) pr…

BI是报表?BI是可视化?BI到底是什么?

很多企业认为只要买一个前端商业智能BI分析工具就可以解决企业级的商业智能BI所有问题&#xff0c;这个看法实际上也不可行的。可能在最开始分析场景相对简单&#xff0c;对接数据的复杂度不是很高的情况下这类商业智能BI分析工具没有问题。但是在企业的商业智能BI项目建设有一…