Spring之依赖注入源码解析

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring 源码解析帮助我们深入理解 Spring 框架的原理和内部实现细节。下面是一个思维图,简单概括了 Spring 源码解析的主要内容: 主要内容: 1. 视图解析(ViewResolver):Spring 源码解析帮助我们了解如何解析和渲染视图,包括不同类型的视图解析器的实现原理和调用顺序。 2. 依赖注入(Dependency Injection):Spring 使用依赖注入来管理对象之间的关系,源码解析帮助我们了解依赖注入实现方式和原理,包括通过 XML 配置和注解的方式进行依赖注入的具体实现。 3. AOP(面向切面编程):Spring 框架支持面向切面编程,源码解析帮助我们了解 AOP 的实现原理和具体操作,包括动态代理和字节码增强的方式。 4. 容器管理(容器生命周期):Spring 框架使用容器来管理对象的生命周期,源码解析帮助我们了解容器的创建、初始化和销毁过程,以及容器中对象的生命周期管理方式。 5. 核心模块分析:Spring 源码解析帮助我们了解核心模块的具体实现原理和关键组件的功能,例如 ApplicationContext、BeanFactory、BeanDefinition 等。 6. 事件驱动编程:Spring 框架提供了事件驱动编程的支持,源码解析帮助我们了解事件的发布和监听机制,以及如何自定义和处理事件。 7. 事务管理:Spring 框架提供了事务管理的支持,源码解析帮助我们了解事务管理的原理和具体实现,包括事务代理和事务传播机制。 8. 数据访问:Spring 源码解析帮助我们了解数据访问的实现原理,包括 JDBC、ORM、事务等相关的具体实现方式和底层技术的使用。 9. Web MVC:Spring 源码解析帮助我们了解 Spring MVC 的实现原理和关键组件的功能,包括处理器映射、处理器适配器、视图解析器等。 10. 单元测试:Spring 源码解析帮助我们了解如何使用单元测试来测试和验证 Spring 框架的各个功能点和组件。 通过对 Spring 源码的深入解析,我们可以更好地理解和使用 Spring 框架,同时也能够提升我们的编程能力和代码质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿小羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值