Spring源码解读---依赖注入源码解析

Posted *King*

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring源码解读---依赖注入源码解析相关的知识,希望对你有一定的参考价值。

Spring中依赖注入的方式

分为两种:手动注入、自动注入

手动注入

在XML中定义Bean时,就是手动注入,手动注入分为两种:set方法注入、构造方法注入

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

<bean name="userService" class="com.example.spring.UserService">
	<property name="orderService" ref="orderService"/>
</bean>

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

<bean name="userService" class="com.example.spring.UserService">
	<constructor‐arg index="0" ref="orderService"/>
</bean>

自动注入

自动注入也分为两种:XML的autowire自动注入、@Autowired注解的自动注入

XML的autowire自动注入

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

autowire的参数有:byType、byName、constructor、default、no

<bean name="userService" class="com.example.spring.UserService" autowire="byType"/>

这样写Spring会自动给UserService中所有的属性自动赋值,不需要在这个属性上加@Autowired注解,但需要这个属性有对应的set方法。

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

PropertyDescriptor中的属性:

  • name:这个name并不是方法名字,而是方法名字进行处理后的名字,如setXXX,那么name=xXX
  • readMethodRef:表示get方法的Method对象的引用
  • readMethodName:表示get方法的名字
  • writeMethodRef:表示set方法的Method对象的引用
  • writeMethodName:表示set方法的名字
  • propertyTypeRef:如果有get方法那么对应的就是返回值类型,如果是set方法那么对应的就是set方法中唯一参数的类型。

autowire的byType和byName情况:

Spring通过ByName自动填充属性时流程:

  1. 找到所有set方法所对应的XXX部分的名字
  2. 根据XXX部分的名字去获取bean

Spring通过ByType自动填充属性时流程:

  1. 获取到set方法中的唯一参数的参数类型,并且根据该类型去容器中获取bean
  2. 如果找到多个会报错

@Autowired注解的自动注入

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

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

@Autowired注解可以写在属性上,构造方法上,set方法上:

  • 写在属性上:先根据属性类型去找Bean,如果找到多个再根据属性名确定一个
  • 构造方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个
  • set方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个

寻找注入点

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

  1. 遍历当前类的所有属性字段Field
  2. 查看字段上是否存在@Autowired、@Value、@Inject中的其中任意一个,存在则认为该字段是一个注入点
  3. 如果字段是static的,则不进行注入
  4. 获取@Autowired中的required属性的值
  5. 将字段信息构造成一个AutowiredFieldElement对象,作为一个注入点对象添加到currElements集合中
  6. 遍历当前类的所有方法Method
  7. 判断当前Method是否是桥接方法,如果是找到原方法
  8. 查看方法上是否存在@Autowired、@Value、@Inject中其中任意一个,存在则认为该方法是一个注入点
  9. 如果方法是static的,则不进行注入
  10. 获取@Autowired中的required属性的值
  11. 将方法信息构造成一个AutowiredMethodElement对象,作为一个注入点对象添加到currElements集合中
  12. 遍历完当前类的字段和方法后,将遍历父类的,直到没有父类
  13. 最后将currElements集合封装成一个InjectionMetadata对象,作为当前Bean对象的注入点集合对象,并缓存

注入点进行注入

Spring在AutowiredAnnotationBeanPostProcessor的postProcessProperties方法中会遍历所找到的注入点依次进行注入

字段注入

  1. 遍历所有的AutowiredFieldElement对象
  2. 将对应的字段封装为DependencyDescriptor对象
  3. 调用BeanFactory的resolveDependency()方法,传入DependencyDescriptor对象,进行依赖查找,找到当前字段所匹配的Bean对象
  4. 将DependencyDescriptor对象和所找到的结果对象beanName封装成一个ShortcutDependencyDescriptor对象作为缓存,如果当前Bean是原型Bean,那么下次再来创建Bean时,就可以直接拿缓存的结果对象beanName去BeanFactory中去拿bean对象了,不用再次进行查找
  5. 利用反射将结果对象赋值给字段

Set方法注入

  1. 遍历所有的AutowiredMethodElement对象
  2. 遍历将对应方法的参数,将每个参数封装成MethodParameter对象
  3. MethodParameter对象封装为DependencyDescriptor对象
  4. 调用BeanFactory的resolveDependency()方法,传入DependencyDescriptor对象,进行依赖查找,找到当前方法参数所匹配的Bean对象
  5. 将DependencyDescriptor对象和所找到的结果对象beanName封装成一个ShortcutDependencyDescriptor对象作为缓存,比如如果当前Bean是原型Bean,那么下次 再来创建该Bean时,就可以直接拿缓存的结果对象beanName去BeanFactory中去那bean对象了,不用再次进行查找了
  6. 利用反射将找到的所有结果对象传给当前方法,并执行

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

在Java反射中,有一个Type接口,表示类型,具体分类为:

  1. raw types:普通Class
  2. parameterized types:对应ParameterizedType接口,泛型类型
  3. array types:对应GenericArrayType,泛型数组
  4. type variables:对应TypeVariable接口,表示类型变量,也就是所定义的泛型,比如T、K
  5. primitive types:基本类型,int、boolean
public class TypeTest 
    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 TypeTest() 
    

    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() + "不是泛型变量");
        
    

打印结果:

i是基本数据类型
i不是泛型类型
i不是普通数组
i不是泛型数组
i不是泛型变量
=======
it不是基本数据类型
it不是泛型类型
it不是普通数组
it不是泛型数组
it不是泛型变量
=======
iarray不是基本数据类型
iarray不是泛型类型
iarray是普通数组
iarray不是泛型数组
iarray不是泛型变量
=======
list不是基本数据类型
list不是泛型类型
list不是普通数组
list不是泛型数组
list不是泛型变量
=======
slist不是基本数据类型
slist是泛型类型
slist不是普通数组
slist不是泛型数组
slist不是泛型变量
=======
tlist不是基本数据类型
tlist是泛型类型
tlist不是普通数组
tlist不是泛型数组
tlist不是泛型变量
=======
t不是基本数据类型
t不是泛型类型
t不是普通数组
t不是泛型数组
t不是泛型变量
=======
tarray不是基本数据类型
tarray不是泛型类型
tarray是普通数组
tarray不是泛型数组
tarray不是泛型变量

@Qualifier的使用

定义两个注解

@Target(ElementType.TYPE, ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Qualifier("random")
public @interface Random 

@Target(ElementType.TYPE, ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Qualifier("roundRobin")
public @interface RoundRobin 


定义一个接口和两个实现类,表示负载均衡

public interface LoadBalance 
	String select();

@Component
@Random
public class RandomStrategy implements LoadBalance 
    @Override
    public String select() 
        return null;
    

@Component
@RoundRobin
public class RoundRobinStrategy implements LoadBalance 
    @Override
    public String select() 
        return null;
    

使用:

@Component
public class UserService 
    @Autowired
    @RoundRobin
    private LoadBalance loadBalance;
    
    public void test() 
    	System.out.println(loadBalance);
    

@Resource注解底层工作流程

以上是关于Spring源码解读---依赖注入源码解析的主要内容,如果未能解决你的问题,请参考以下文章

Spring源码解读---依赖注入源码解析

Spring-IOC源码解读3-依赖注入

【dubbo源码】13. 服务消费方之@Reference依赖注入原理

Spring源码解读---循环依赖底层源码解析

Spring源码解读---循环依赖底层源码解析

Spring源码解读---循环依赖底层源码解析