Spring 中 @Autowired 和 @Resource 有什么区别?

简介: 背景做为一名 Java 程序员,日常开发中使用最多的便是 Spring,工作了很多年,很多人都停留在使用的层面上,甚至连最基本的概念都没搞懂。笔者在 Java 领域也辛勤耕耘了几年,为了避免浮于表面,在今年6月份开始看 Spring 的源码,其优秀的设计确实值得每一个 Java 开发者去学习。

背景


做为一名 Java 程序员,日常开发中使用最多的便是 Spring,工作了很多年,很多人都停留在使用的层面上,甚至连最基本的概念都没搞懂。笔者在 Java 领域也辛勤耕耘了几年,为了避免浮于表面,在今年6月份开始看 Spring 的源码,其优秀的设计确实值得每一个 Java 开发者去学习。使用 Spring 进行依赖注入我们最常使用的注解是 @Autowired,最近有同事用到了 @Resource 注解,可能了解到我在看 Spring,因此有向我请教和 @Autowired 有什么区别。鉴于多数人可能对这两者的区别不甚理解,而网上大多数文章又只给出结论,甚至结论中也存在一些错误,因此有必要开一篇文章进行深入分析。知其然,还要知其所以然。


@Autowired 和 @Resource 的区别


这里先给出结论,后面我们将会带着结论去分析,结论到底是否正确。


区别一:所属不同。


@Autowired 是 spring-beans 模块提供的注解。

@Resource 是 JSR 250 规范提出的注解,由 JDK 自带。


区别二:装配方式不同。两者都可以标注在属性或 setter 方法上。


@Autowired 注解只能按照类型装配依赖,如果需要按照名称装配还需要指定 @Qualifier 注解。

@Resource 默认依赖 bean 的名称为属性名,并且可以通过其属性 name 进行指定。默认依赖的 bean 的类型为属性的类型,并且可以通过 type 属性进行指定。 如果未指定依赖的 bean 的名称并且属性名对应的 bean 在容器中不存在时才会按照类型进行注入,否则按照名称进行注入依赖。


区别三:是否强依赖不同。


@Autowired 指定的依赖默认必须存在,可以通过 requied 属性指定不存在。

@Resource 指定的依赖必须存在。


原理分析


@Autowired 注入分析


Spring 使用 AutowiredAnnotationBeanPostProcessor 对 @Autowired 进行处理。具体来说注入属性的方法位于 AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject,注入方法参数的方法位于 AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement#inject,它们的处理方式基本类似。以注入属性的流程为例进行分析,查看属性注入相关代码如下。


  private class AutowiredMethodElement extends InjectionMetadata.InjectedElement {
    public AutowiredFieldElement(Field field, boolean required) {
      super(field, null);
      this.required = required;
    }
    // 注入属性
    @Override
    protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
      Field field = (Field) this.member;
      Object value;
      if (this.cached) {
        value = resolvedCachedArgument(beanName, this.cachedFieldValue);
      } else {
        // 将属性转换为依赖描述符
        DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
        desc.setContainingClass(bean.getClass());
        Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
        Assert.state(beanFactory != null, "No BeanFactory available");
        TypeConverter typeConverter = beanFactory.getTypeConverter();
        try {
          // 解析依赖
          value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
        } catch (BeansException ex) {
          throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
        }
        ... 省略部分代码
      }
      if (value != null) {
        ReflectionUtils.makeAccessible(field);
        field.set(bean, value);
      }
    }
  }


可以看到,Spring 对 @Autowired 标注的属性的注入只是将属性转换为依赖描述符 DependencyDescriptor ,然后调用 BeanFactory 解析依赖的方法。转换为依赖描述符时需要指定是否依赖是必须的,这个值在实例化 AutowiredFieldElement 时指定。AutowiredAnnotationBeanPostProcessor 实例化 AutowiredFieldElement 的相关代码如下。


  // 注解属性名
  private String requiredParameterName = "required";
  // 依赖必须存在的属性值
  private boolean requiredParameterValue = true;
  // 构建注入元数据
  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())) {
            ... 省略日志打印
            return;
          }
          // 循环属性,将 @Autowired 标注的属性转换为 AutowiredFieldElement
          boolean required = determineRequiredStatus(ann);
          currElements.add(new AutowiredFieldElement(field, required));
        }
      });
      ... 省略方法参数上 @Autowired 注解的处理
      elements.addAll(0, currElements);
      targetClass = targetClass.getSuperclass();
    }
    while (targetClass != null && targetClass != Object.class);
    return InjectionMetadata.forElements(elements, clazz);
  }
  // 判断依赖是否必须存在
  protected boolean determineRequiredStatus(MergedAnnotation<?> ann) {
    // The following (AnnotationAttributes) cast is required on JDK 9+.
    return determineRequiredStatus((AnnotationAttributes)
        ann.asMap(mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType())));
  }
  // 判断依赖是否必须存在
  @Deprecated
  protected boolean determineRequiredStatus(AnnotationAttributes ann) {
    return (!ann.containsKey(this.requiredParameterName) ||
        this.requiredParameterValue == ann.getBoolean(this.requiredParameterName));
  }


Spring 构建注入元数据时通过反射获取属性,然后将属性转换为 AutowiredFieldElement ,而 required 的值的获取则会读取注解中的 required 属性,如果不存在或值为 true 则依赖必须存在。


至此,可以看出 @Autowired 的处理只是把属性或方法参数转换为依赖描述符,然后调用 BeanFactory 依赖注入的方法,至于依赖是否必须存在则可以由 @Autowired 的 required 属性值进行指定。AutowireCapableBeanFactory#resolveDependency 方法完成真正的依赖注入,其会根据依赖的类型注入 0 到多个 bean 。例如如果依赖是一个 Map,则 Spring 会把 Map 值类型对应的 bean 全部存在到 map 中,而如果依赖的是普通的类型而 Spring 中存在多个相同类型的 bean,Spring 又无法确定使用哪一个则需要使用 @Primary 指定主要的 bean 或使用 @Qualifier 指定依赖的 bean 的名称。由于篇幅问题,感兴趣的小伙伴可自行查阅相关代码。


@Resource 注入分析


除了对 Spring 自带注解的支持,Spring 对 JSR 250 等各种规范也进行了支持,对 @Resource 的处理 Spring 使用 CommonAnnotationBeanPostProcessor 进行。注入 @Resource 标注的属性或方法的相关代码如下。


public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
    implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
  protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)
      throws NoSuchBeanDefinitionException {
    Object resource;
    Set<String> autowiredBeanNames;
    String name = element.name;
    if (factory instanceof AutowireCapableBeanFactory) {
      AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
      DependencyDescriptor descriptor = element.getDependencyDescriptor();
      // fallbackToDefaultTypeMatch 默认为 true, isDefaultName 表示是否使用默认的属性名称,name 表示属性名称
      if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
        // 优先按照 bean 名称进行依赖注入,不存在则按照类型进行注入
        autowiredBeanNames = new LinkedHashSet<>();
        resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
        if (resource == null) {
          throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
        }
      } else {
        resource = beanFactory.resolveBeanByName(name, descriptor);
        autowiredBeanNames = Collections.singleton(name);
      }
    } else {
      resource = factory.getBean(name, element.lookupType);
      autowiredBeanNames = Collections.singleton(name);
    }
    if (factory instanceof ConfigurableBeanFactory) {
      ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory;
      for (String autowiredBeanName : autowiredBeanNames) {
        if (requestingBeanName != null && beanFactory.containsBean(autowiredBeanName)) {
          beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
        }
      }
    }
    return resource;
  }
}


可以看到,Spring 会对根据 LookupElement 进行判断,如果使用默认的属性名,并且属性名在 Spring 中不存在对应的 bean,则会委托给 AutowireCapableBeanFactory#resolveDependency 进行处理,此时和 @Autowired 注解保持一致。如果 @Resource 指定了 bean 名称或者属性名对应的 bean 在容器中存在,Spring 会按照名称进行依赖注入。而非绝对的未指定名称则按照类型注入,这点需要注意。


总结

本篇介绍了 Spring 依赖注入中 @Autowired 和 @Resource 的区别,并从源码角度进行了分析。网上的博客质量参差不齐,初学 Spring 时我也在网上查了一些资料,一度认为 @Resource 未指定名称就会按照类型进行注入,这种错误的想法在看到相关源码之后自然能够一眼辨认出来,希望大家看网上文章时时刻持有怀疑态度,经过验证之后才能确认。最后欢迎大家留言评论交流。


目录
相关文章
|
1月前
|
Java 测试技术 程序员
为什么Spring不推荐@Autowired用于字段注入?
作为Java程序员,Spring框架在日常开发中使用频繁,其依赖注入机制带来了极大的便利。然而,尽管@Autowired注解简化了依赖注入,Spring官方却不推荐在字段上使用它。本文将探讨字段注入的现状及其存在的问题,如难以进行单元测试、违反单一职责原则及易引发NPE等,并介绍为何Spring推荐构造器注入,包括增强代码可读性和维护性、方便单元测试以及避免NPE等问题。通过示例代码展示如何将字段注入重构为构造器注入,提高代码质量。
|
1月前
|
监控 Java 应用服务中间件
Spring和Spring Boot的区别
Spring和Spring Boot的主要区别,包括项目配置、开发模式、项目依赖、内嵌服务器和监控管理等方面,强调Spring Boot基于Spring框架,通过约定优于配置、自动配置和快速启动器等特性,简化了Spring应用的开发和部署过程。
54 19
|
1月前
|
Java 编译器 Spring
Spring AOP 和 AspectJ 的区别
Spring AOP和AspectJ AOP都是面向切面编程(AOP)的实现,但它们在实现方式、灵活性、依赖性、性能和使用场景等方面存在显著区别。‌
79 2
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
131 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
3月前
|
Java 数据库连接 数据库
Spring Data JPA 与 Hibernate 之区别
【8月更文挑战第21天】
91 0
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
1月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
175 2
|
16天前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
27 2
 SpringBoot入门(7)- 配置热部署devtools工具
|
12天前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
24 2
下一篇
无影云桌面