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 未指定名称就会按照类型进行注入,这种错误的想法在看到相关源码之后自然能够一眼辨认出来,希望大家看网上文章时时刻持有怀疑态度,经过验证之后才能确认。最后欢迎大家留言评论交流。


目录
相关文章
|
Java 测试技术 程序员
为什么Spring不推荐@Autowired用于字段注入?
作为Java程序员,Spring框架在日常开发中使用频繁,其依赖注入机制带来了极大的便利。然而,尽管@Autowired注解简化了依赖注入,Spring官方却不推荐在字段上使用它。本文将探讨字段注入的现状及其存在的问题,如难以进行单元测试、违反单一职责原则及易引发NPE等,并介绍为何Spring推荐构造器注入,包括增强代码可读性和维护性、方便单元测试以及避免NPE等问题。通过示例代码展示如何将字段注入重构为构造器注入,提高代码质量。
746 1
|
负载均衡 Dubbo Java
Spring Cloud Alibaba与Spring Cloud区别和联系?
Spring Cloud Alibaba与Spring Cloud区别和联系?
|
Java 关系型数据库 数据库
京东面试:聊聊Spring事务?Spring事务的10种失效场景?加入型传播和嵌套型传播有什么区别?
45岁老架构师尼恩分享了Spring事务的核心知识点,包括事务的两种管理方式(编程式和声明式)、@Transactional注解的五大属性(transactionManager、propagation、isolation、timeout、readOnly、rollbackFor)、事务的七种传播行为、事务隔离级别及其与数据库隔离级别的关系,以及Spring事务的10种失效场景。尼恩还强调了面试中如何给出高质量答案,推荐阅读《尼恩Java面试宝典PDF》以提升面试表现。更多技术资料可在公众号【技术自由圈】获取。
|
监控 Java 应用服务中间件
Spring和Spring Boot的区别
Spring和Spring Boot的主要区别,包括项目配置、开发模式、项目依赖、内嵌服务器和监控管理等方面,强调Spring Boot基于Spring框架,通过约定优于配置、自动配置和快速启动器等特性,简化了Spring应用的开发和部署过程。
921 19
|
Java 编译器 Spring
Spring AOP 和 AspectJ 的区别
Spring AOP和AspectJ AOP都是面向切面编程(AOP)的实现,但它们在实现方式、灵活性、依赖性、性能和使用场景等方面存在显著区别。‌
841 2
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
4624 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1509 0
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
1481 0
|
前端开发 Java 数据库
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——Thymeleaf 介绍
本课介绍Spring Boot集成Thymeleaf模板引擎。Thymeleaf是一款现代服务器端Java模板引擎,支持Web和独立环境,可实现自然模板开发,便于团队协作。与传统JSP不同,Thymeleaf模板可以直接在浏览器中打开,方便前端人员查看静态原型。通过在HTML标签中添加扩展属性(如`th:text`),Thymeleaf能够在服务运行时动态替换内容,展示数据库中的数据,同时兼容静态页面展示,为开发带来灵活性和便利性。
630 0
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于 xml 的整合
本教程介绍了基于XML的MyBatis整合方式。首先在`application.yml`中配置XML路径,如`classpath:mapper/*.xml`,然后创建`UserMapper.xml`文件定义SQL映射,包括`resultMap`和查询语句。通过设置`namespace`关联Mapper接口,实现如`getUserByName`的方法。Controller层调用Service完成测试,访问`/getUserByName/{name}`即可返回用户信息。为简化Mapper扫描,推荐在Spring Boot启动类用`@MapperScan`注解指定包路径避免逐个添加`@Mapper`
1209 0