自学内容网 自学内容网

Spring依赖注入

Spring注入方式

Spring有以下两种注入方式:

  1. 手动注入
  2. 自动注入

手动注入

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

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

上面这种方式底层就是通过 set方法 进行注入,UserService里面必须要有orderService的set方法.没有会报错

<bean id="orderService" class="com.fanqiechaodan.service.OrderService"></bean>
<bean id="userService" class="com.fanqiechaodan.service.UserService" >
    <constructor-arg index="0" ref="orderService"/>
</bean>

上面这种底层通过 构造方法 进行注入,UserService里面要有对应的构造方法,没有会报错

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

  1. set方法注入
  2. 构造方法注入

自动注入

自动注入分为以下两种:

  1. XML的autowire自动注入
  2. @Autowired注解的自动注入

XML的autowire自动注入

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

  1. byType
  2. byName
  3. constructor
  4. default
  5. no
<bean id="orderService" class="com.fanqiechaodan.service.OrderService"></bean>
<bean id="userService" class="com.fanqiechaodan.service.UserService" autowire="byType"></bean>

这么写,表示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. 根据名字去获取Bean

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

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

如果是constructor,那么就可以不写set方法了,当某个Bean是通过构造方法来注入时,Spring利用构造方法参数信息从Spring容器中去找Bean,找到Bean之后作为参数传给构造方法,从而实例化得到一个Bean对象,并完成属性赋值,其实构造方法注入相当于byType+byName,普通的byType是根据set方法中的参数类型去找Bean.找到多个会报错,而constructor就是通过构造方法中的参数类型去找Bean,如果找到多个会根据参数名确定.

另外两个:

  1. no:关闭autowire
  2. default:默认值,如果在<bean>设置了autowire为default,则会使用<beans>中设置的autowire

@Autowired注解的自动注入

从本质上讲,@Autowired注解提供了与autowire相同的功能,但是拥有 更细粒度的控制 和广泛的适用性

XML中的autowire控制的是整个Bean的所有属性,而@Autowired注解是直接写在某个属性,某个set方法,某个构造方法上的.如果一个类有多个构造方法,XML的autowire=constructor,无法指定用那个构造方法,用@Autowired注解可以指定构造方法.

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

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

@Autowired注解可以写在:

  1. 属性上(属性注入):先根据 属性类型 去找Bean,如果找到多个再根据 属性名 确定一个
  2. set方法上(set方法注入):先根据方法 参数类型 去找Bean,如果找到多个再根据 参数名 确定一个
  3. 构造方法上(构造方法注入): 先根据方法 参数类型 去找Bean,如果找到多个再根据 参数名 确定一个

寻找注入点

在创建一个Bean的过程中,Spring会利用org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition找到注入点并缓存,找注入点的流程为:

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

对应的源码实现

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    metadata.checkConfigMembers(beanDefinition);
}

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
    // Fall back to class name as cache key, for backwards compatibility with custom callers.
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    // Quick check on the concurrent map first, with minimal locking.
    InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                metadata = buildAutowiringMetadata(clazz);
                this.injectionMetadataCache.put(cacheKey, metadata);
            }
        }
    }
    return metadata;
}

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
        return InjectionMetadata.EMPTY;
    }

    List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
    Class<?> targetClass = clazz;

    do {
        final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

        ReflectionUtils.doWithLocalFields(targetClass, field -> {
            MergedAnnotation<?> ann = findAutowiredAnnotation(field);
            if (ann != null) {
                if (Modifier.isStatic(field.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static fields: " + field);
                    }
                    return;
                }
                boolean required = determineRequiredStatus(ann);
                currElements.add(new AutowiredFieldElement(field, required));
            }
        });

        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }
            MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
            if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                if (Modifier.isStatic(method.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static methods: " + method);
                    }
                    return;
                }
                if (method.getParameterCount() == 0) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation should only be used on methods with parameters: " +
                                    method);
                    }
                }
                boolean required = determineRequiredStatus(ann);
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                currElements.add(new AutowiredMethodElement(method, required, pd));
            }
        });

        elements.addAll(0, currElements);
        targetClass = targetClass.getSuperclass();
    }
    while (targetClass != null && targetClass != Object.class);

    return InjectionMetadata.forElements(elements, clazz);
}

static的字段或方法为什么不支持?

再Spring中,依赖注入主要针对对象级别的依赖关系进行管理,即在对象创建时通过构造函数,Setter方法或者字段注入等方式将依赖项注入到对象中.而静态字段属于类级别的属性,通常用于保存类级别的状态或者常量值,而不是对象之间的依赖关系.同样静态方法同样属于类级别的方法,它们可以直接通过类名调用,而不需要创建对象的实例.因此Spring的设计理念主要集中在对象之间的依赖关系管理,而对于静态字段/方法并不是其主要关注点.不支持static的字段/方法进行注入.

注入点进行注入

Spring在org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties方法中,遍历所找到的注入点,依次进行注入.

字段注入

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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述在这里插入图片描述

set方法注入

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


原文地址:https://blog.csdn.net/qq_43135259/article/details/139111982

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!