转自大钉钉应用研发平台的处轩同学, 稍加整理与补充, 原文链接: 既生@Resource,何生@Autowired,是Spring官方没事做?

官方定义

javax.annotation.Resource

@Resource 于 2006 随着 JSR-250 发布, 官方解释是:

Resource 注释标记了应用程序需要的资源; 该注解可以应用于应用程序组件类, 或组件类的字段或方法; 当注解应用于字段或方法时, 容器将在组件初始化时将所请求资源的实例注入到应用程序组件中; 如果注释应用于组件类, 则注释声明应用程序将在运行时查找的资源;

可以看到它是一个通用定义, 由第三方组件或框架自主实现;

org.springframework.beans.factory.annotation.Autowired

@Autowired 于 2007 年随着 spring 2.5 发布, 同时官方也对 @Resource 进行了支持; @Autowired 的官方解释是:

将构造函数、字段、设置方法或配置方法标记为由 spring 的依赖注入工具自动装配;

可以看到, @Autowired 是 spring 的亲儿子, 而 @Resource 是 spring 对它定义的一种实现, 它们的功能如此相似; 那么为什么要支持了 @Resource, 又要自己搞个 @Autowired 呢?

为此专门查了一下 spring 2.5 的官方文档, 文档中有一段这么说到:

However, Spring 2.5 dramatically changes the landscape. As described above, the autowiring choices have now been extended with support for the JSR-250 @Resource annotation to enable autowiring of named resources on a per-method or per-field basis. However, the @Resource annotation alone does have some limitations. Spring 2.5 therefore introduces an @Autowired annotation to further increase the level of control.

大概的意思是说, spring 2.5 开始支持注解自动装配, 现已经支持 JSR-250 @Resource 基于每个方法或每个字段的命名资源的自动装配, 但是只有 @Resource 是不行的, 我们还推出了 “粒度” 更大的 @Autowired, 来覆盖更多场景;
那么 “粒度 “指的是什么呢? 

本质区别

要想找到粒度是什么, 我们先从两个注解的功能下手:

  • @Autowired: 按照类型注入
  • @Resource: 按照名字注入优先, 若找不到再根据名字找类型

若要论功能的 “粒度”, 按理说 @Resource 已经包含 @Autowired 了, 难道是 spring 2.5 的时候还不支持? 再次翻阅 spring 2.5 的文档, 上面明确的写到 @Resource 的功能和现在是一致的:

When using @Resource without an explicitly provided name, if no Spring-managed object is found for the default name, the injection mechanism will fallback to a type-match.

那么 “粒度” 到底指的是什么? stackoverflow 有一个回答似乎指出了关键所在:

Both @Autowired and @Resource work equally well. But there is a conceptual difference or a difference in the meaning:
— @Resource means get me a known resource by name. The name is extracted from the name of the annotated setter or field, or it is taken from the name-Parameter.
— @Inject or @Autowired try to wire in a suitable other component by type.
So, basically these are two quite distinct concepts. Unfortunately the Spring-Implementation of @Resource has a built-in fallback, which kicks in when resolution by-name fails. In this case, it falls back to the @Autowired-kind resolution by-type. While this fallback is convenient, IMHO it causes a lot of confusion. because people are unaware of the conceptual difference and tend to use @Resource for type-based autowiring.

spring 虽然实现了两个类似的功能, 但是存在概念或含义上的差异:

  • @Autowired 尝试按类型匹配合适的组件;
  • @Resource 意图按名称直接获取一个确定的资源, 尽管当 @Resource 按名称解析失败时它会按类型解析, 但这只是 spring 提供给开发者的一种便利, 甚至不排除在未来的 spring 版本中取消这一特性;

所以 Spring 官方说的粒度是指 “资源范围”:

  • @Resource 默认寻找的是确定的的资源, 相当于给你一个坐标, 你直接去获取;
  • @Autowired 则是在一片区域里面尝试搜索并匹配合适的资源;

如何佐证

stackoverflow 的回答很有道理, 但毕竟不是官方正式说明, 如果能从其他角度找到一些证据, 将更有说服力;

证据一: 集合注入场景的区别

不同注解集合注入的区别
不同注解集合注入的区别

使用 @Autowired 在左侧就有 IEAD 的小绿标, 而使用 @Resource 就不会有 (尽管 @Resource 最终也能生效), 因为本质上集合注入不是单一、也是不确定性的;
@Resource 没有出现小绿标证明了其本意并非要做基于类型的搜索及匹配;

证据二: 使用 @Primary 场景的区别
当存在两个类型相同的 spring bean, 若不以 bean name 加以区分, 在使用 @Resource 注入时便会报 NoUniqueBeanDefinitionException; 尽管 @Autowired 注入也一样存在该问题, 不过只要使用 @Primary 注解指定其中一个 bean 为优先的, @Autowired 就能使用该优先 bean 注入成功; 但是 @Resource 却不能识别 @Primary 注解并匹配到高优先级的 bean, 这直接证明了其按类型匹配的能力相比于 @Autowired 也是不完整的;

在理解了其中本质区别后, 就自然引出了另外两个问题:
spring 为什么要支持两个功能相似的注解呢?

  • 为了方便其他框架迁移: @Resource 是一种通用规范, 只要符合 JSR-250 的其他框架, spring 就可以兼容;

既然 @Resource 更倾向于找已知资源, 为什么也有按类型注入的功能?

  • 为了全面兼容从其他框架切换到 spring, 开发者就算只使用 @Resource 也能保持 spring 强大的依赖注入功能;

最佳实践

在日常写代码的时候, 使用 @Autowired 进行属性注入的时候 IDEA 会报出黄色警告, 并且推荐我们使用构造方法注入, 这是为什么呢?

@Autowired 报警
@Autowired 报警

主要原因有几点:

  1. 基于属性的依赖注入不适用于声明为 final 的字段
    因为此字段必须在类实例化时才能实例化; 声明不可变依赖项的唯一方法是使用基于构造函数的依赖项注入;
  2. 容易忽视类的单一原则
    一个类应该只负责软件应用程序功能的单个部分, 并且它的所有服务都应该与该职责紧密结合; 如果使用属性的依赖注入, 在你的类中很容易有很多依赖, 一切看起来都很正常; 但是如果改用基于构造函数的依赖注入, 随着更多的依赖被添加到你的类中, 构造函数会变得越来越大, 代码开始就开始出现异味, 发出明确的信号表明有问题; 具有超过十个参数的构造函数清楚地表明该类有太多的依赖, 让你不得不注意该类的单一问题了; 因此属性注入虽然不直接打破单一原则, 但它却会诱导你忽视单一原则;
  3. 不易发现循环依赖
    A 类通过构造函数注入需要 B 类的实例, B 类通过构造函数注入需要 A 类的实例; 如果你为类 A 和 B 配置 bean 以相互注入, 使用构造方法就能很快发现;
  4. 依赖赋值强依赖 IoC 容器
    如果您想在容器之外使用这的类, 例如用于单元测试, 不得不使用 spring 容器来实例化它, 因为没有其他可能的方法 (除了反射) 来设置自动装配的字段;

但为什么使用 @Resource 进行属性注入没有 IDEA 的黄色告警呢?

  • @Autowired 是 spring 提供的, 无法在其他 IoC 框架内支持; 所以当使用 @Autowired, IDEA 判断当前项目没有迁移到其他 IoC 框架的可能性, 那么 IDEA 便建议开发者使用 spring 框架下更优的解决方案 (构造器注入);
  • 而 @Resource 是 JSR-250 标准, 当使用 @Resource, IDEA 判断当前项目存在切换到其他 IoC 容器的可能性; 在不知道其他 IoC 容器是否支持构造器注入的情况下, IDEA 不会冒然建议开发者更改注入方式;

构造器注入的简化

尽管 IDEA 不会对使用 @Resource 的属性注入给出黄色警告, 但毕竟属性注入有大量缺点, 所以我们也不建议使用 @Resource, 而是建议使用构造器注入;
有人会说, 使用构造器注入相比属性注入, 需要额外写一个有参构造方法, 造成了不必要的工作量; 其实这个问题可以用 lombok 解决:

1
2
3
4
5
6
7
8
9
10
11
@Service
@RequiredArgsConstructor
public class XXXService {
private final AService aService;
private final BService bService;

public void xxxService() {
aService.method1();
bService.method2();
}
}

需要注意的是: 以上方法尽管避免了有参构造器的显式编码, 但却再次引入了与属性注入相同的两个问题:

  • 容易忽视类的单一原则;
  • 不易发现循环依赖;

所以在项目中究竟要采用何种方案, 就见仁见智了!

参考链接