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
)
Spring 在通过
byName
自动填充属性时的流程是:
1、找到所有 set
方法所对应的 XXX 部分的名字
2、根据 XXX 部分的名字去获取 bean
Spring 在通过
byType
自动填充属性时的流程是:
1、获取到 set 方法中的唯一参数的参数类型,并根据该类型去容器中获取对应的 Bean
2、如果找到多个,则会报错
分析了 autowire 的 byType
和 byName
的情况,接下来分析 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()
找到注入点并缓存,找注入点的流程为:
1、遍历当前类中的所有属性字段 Field
2、查看属性字段上是否存在 @Autowired
、@Value
、@Inject
中的其中一个,如果存在则认为该字段是一个注入点
3、如果字段是 static 的,则不进行自动注入
4、获取 @Autowired
中的 required 属性的值
5、将字段信息构造成一个 AutowiredFieldElement
对象,作为一个注入点对象添加到 currElements
集合中。
6、遍历当前类中的所有方法 Method
7、判断当前方法是否是桥接方法,如果是则找到原方法
8、查看方法上是否存在 @Autowired
、@Value
、@Inject
中的其中一个,如果存在则认为该方法是一个注入点
9、如果方法是static的,则不进行自动注入
10、获取 @Autowired
注解中的 required 属性的值
11、将方法信息构造成一个 AutowiredMethodElement
对象,作为一个注入点对象添加到 currElements
集合中。
12、遍历完当前类的属性和方法后,将继续遍历其父类的属性和方法,直到没有父类。
13、最后将 currElements
集合封装成一个 InjectionMetadata
对象,作为当前 Bean 对应的注入点集合对象,并缓存。
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()
方法中,会遍历所有找到的注入点并依次进行注入。
属性注入
1、遍历所有的 AutowiredFieldElement
对象。
2、将对应的字段封装成 DependencyDescriptor
对象。
3、调用 BeanFactory 的 resolveDependency()
方法,传入 DependencyDescriptor 对象,进行依赖查找,找到当前字段所匹配的 Bean 对象。
4、将 DependencyDescriptor 对象和所找到的 Bean 对象的名字(BeanName)封装成一个 ShortcutDependencyDescriptor
对象作为缓存,比如当前 Bean是原型 Bean,那么下次再来创建该 Bean 时,就可以直接拿缓存中的 Bean 对象的名字(BeanName)去 BeanFactory 中获取 Bean 对象了,不用再次进行查找了。
5、利用反射将获取到的Bean对象赋值给字段。
Set方法注入
1、遍历所有的 AutowiredMethodElement 对象。
2、遍历对应的方法参数,将每个参数封装成 MethodParameter 对象。
3、将 MethodParameter 对象封装成 DependencyDescriptor 对象。
4、调用 BeanFactory 的 resolveDependency()
方法,传入 DependencyDescriptor 对象,进行依赖查找,找到当前方法参数所匹配的 Bean 对象。
5、将 DependencyDescriptor 对象和所找到的 Bean 对象的名字(BeanName)封装成一个 ShortcutDependencyDescriptor 对象作为缓存,比如当前 Bean 是原型 Bean,那么下次再来创建该 Bean 时,就可以直接拿缓存中的 Bean 对象的名字(BeanName)去 BeanFactory 中获取 Bean 对象了,不用再次进行查找了。
6、利用反射将找到的所有结果对象传给当前方法,并执行。
5、核心方法详解
5.1、resolveDependency( )实现
/**
* 点进去看看 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
1、找出 BeanFactory 中类型为 type 的所有 Bean 的名字,注意是名字(BeanName),而不是 Bean 对象,因为我们可以根据 BeanDefinition 就能判断和当前 type 是不是匹配,不用生成 Bean 对象
2、把 resolvableDependencies 中 key 为 type 的对象找出来并添加到 result 中
3、遍历根据 type 找出的 beanName,判断当前 beanName 对应的 Bean 是不是能够被自动注入
4、先判断 beanName 对应的 BeanDefinition 中的 autowireCandidate 属性,如果为 false 表示不能用来进行自动注入,如果为 true 则继续进行判断
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