Featured image of post Spring 依赖注入:@Resource 与 @Autowired 的使用边界

Spring 依赖注入:@Resource 与 @Autowired 的使用边界

两个最常用的 Spring 注入注解,差异比看上去多。本文从原理、源头、行为细节讲清楚什么时候该用哪个

老生常谈但很少人答得明白的一题

Spring 项目里随手就能写出这两种依赖注入:

1
2
3
4
5
@Autowired
private UserService userService;

@Resource
private OrderService orderService;

两个注解效果几乎一样。但面试官一问"两者区别",多数人只能磕磕巴巴答出"前者按类型,后者按名字"——这只是冰山一角

本文把这两个注解的差异、源头、行为细节、踩坑点系统讲清楚。


一、出处不同

@Autowired@Resource
出处Spring 自家JSR-250(Java EE 标准)
包路径org.springframework.beans.factory.annotationjavax.annotation / jakarta.annotation
历史Spring 2.5+Java EE 5(2006)
跨框架只有 Spring 认Spring / CDI / 多框架认

@Resource 是 Java 标准注解,@Autowired 是 Spring 私货。这是两者最本质的差异——理论上,使用 @Resource 的代码可以无缝切到任何 JSR-250 兼容的容器;用 @Autowired 就只能跑在 Spring 上。

实际工程里这点意义不大——99% 的 Java Web 项目都跑在 Spring 上。


二、查找规则不同

这是最常被问的差异——但远不止"按类型 vs 按名字"。

@Autowired 的查找顺序

简化:先按类型,类型多个时退化到按名字

@Resource 的查找顺序

简化:先按名字(字段名),找不到再按类型

行为差异举例

1
2
3
4
5
6
7
public interface MessageSender {}

@Component
public class SmsSender implements MessageSender {}

@Component
public class EmailSender implements MessageSender {}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// @Autowired:类型 MessageSender 有两个 Bean,先按类型找不唯一
@Autowired
private MessageSender smsSender;        // ✓ 退化到字段名匹配 SmsSender

@Autowired
private MessageSender provider;         // ❌ 字段名没匹配,抛异常

// @Resource:默认按字段名找
@Resource
private MessageSender smsSender;        // ✓ 直接按 smsSender 找到

@Resource
private MessageSender provider;         // ❌ 字段名没匹配的 Bean

// @Resource 显式指定
@Resource(name = "smsSender")
private MessageSender provider;         // ✓

三、required 选项

@Autowiredrequired 属性

1
2
@Autowired(required = false)
private OptionalService optionalService;   // 没有 Bean 时注入 null

@Resource 没有这个开关——Bean 找不到一定抛异常。

不过现代 Spring 推荐用 Optional 替代:

1
2
@Autowired
private Optional<OptionalService> service;

四、能否注入到构造器 / 方法

1
2
3
4
5
6
7
8
@Autowired
public OrderService(UserService userService) { ... }    // ✓ 构造器

@Autowired
public void setUserService(UserService userService) { ... }    // ✓ Setter

@Autowired
public void initSomething(UserService u, OrderService o) { ... }    // ✓ 任意方法

@Resource 主要用于字段和 Setter,不能注入到构造器:

1
2
@Resource
public OrderService(UserService userService) { ... }    // ❌ 不支持

这是个关键差别——构造器注入是 Spring 推荐的方式(不可变、便于测试),但 @Resource 不支持。所以"全用 @Resource 替代 @Autowired“在构造器场景下行不通。


五、集合注入

@Autowired 能优雅注入集合

1
2
3
4
5
@Autowired
private List<MessageSender> senders;       // 所有实现都被注入

@Autowired
private Map<String, MessageSender> senderMap;   // key 是 Bean name

@Resource 在集合注入上行为不那么友好——没有"按类型找全部"的语义。

这是 @Autowired 在写"策略模式"时的杀手锏——业务代码:

1
2
3
4
5
6
@Autowired
private Map<String, PaymentChannel> channels;

public void pay(String type, ...) {
    channels.get(type).charge(...);
}

加一个新支付渠道只需要 @Component,业务代码无感。


六、构造器注入 vs 字段注入

无论用哪个注解,字段注入是反模式。Spring 官方文档现在也推荐构造器注入:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ❌ 字段注入(反模式)
@Autowired
private UserService userService;

// ✓ 构造器注入
private final UserService userService;

public OrderService(UserService userService) {
    this.userService = userService;
}

如果用 Lombok 更简洁:

1
2
3
4
5
6
@RequiredArgsConstructor
@Service
public class OrderService {
    private final UserService userService;
    private final OrderRepository repo;
}

构造器注入的好处:

  • 字段可以 final——不可变
  • 无 Spring 也能 new 出来——纯 Java,方便单测
  • 依赖关系暴露在签名上——一眼能看出这个类依赖什么
  • 循环依赖会立刻报错——而不是"运行时 NPE”

这就是为什么"@Autowired vs @Resource“这场争论某种意义上已经过时了——两者都不该用在字段上


七、@Qualifier 与 @Resource(name)

类型多个 Bean 时,两种注解各有指定方式:

1
2
3
4
5
6
@Autowired
@Qualifier("smsSender")
private MessageSender sender;

@Resource(name = "smsSender")
private MessageSender sender;

效果相同。@Qualifier 还可以自定义:

1
2
3
4
5
6
7
@Component
@Qualifier("primary-sms")
public class AlibabaSmsSender implements MessageSender {}

@Autowired
@Qualifier("primary-sms")
private MessageSender sender;

@Resource(name) 没有这种"分组标签"的扩展能力。


八、循环依赖处理

两者对循环依赖的处理一致——字段注入setter 注入支持循环依赖(Spring 三级缓存);构造器注入循环依赖直接报错。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// A 依赖 B
@Service
public class A {
    @Autowired private B b;
}

// B 依赖 A → Spring 三级缓存可以解
@Service
public class B {
    @Autowired private A a;
}

生产代码里出现循环依赖通常意味着设计问题,不要因为 Spring 能 handle 就放任。


九、@Autowired vs @Inject

讲到 JSR 标准,绕不过 @Inject(JSR-330):

注解出处required 默认
@AutowiredSpringtrue
@ResourceJSR-250true
@InjectJSR-330true

@Inject 是 Google Guice / CDI 的标准,Spring 也支持——实际工程里基本没人用,因为 Spring 自家 @Autowired 功能更全。


十、踩坑提醒

1. 接口多实现时

1
2
@Autowired
private Service service;   // 多个实现时报错

要么字段名匹配某个 Bean,要么显式 @Qualifier,要么用 @Primary

1
2
3
@Component
@Primary
public class DefaultService implements Service {}

2. final 字段不能用 @Autowired

1
2
@Autowired
private final UserService userService;   // ❌

final 字段必须在构造器里初始化——直接用构造器注入。

3. static 字段不能注入

1
2
@Autowired
private static UserService userService;   // ❌ 不会注入

要给 static 字段注入只能在 setter 里手动 set,但这通常是设计有问题。

4. 单例引用原型 Bean

1
2
3
4
5
@Component   // 默认单例
public class A {
    @Autowired
    private B b;     // B 是 prototype,但 A 单例 → 始终是同一个 B 实例
}

不是 Spring 的 bug——是单例的依赖只注入一次。要每次取新实例:

1
2
3
4
5
6
@Autowired
private ObjectProvider<B> bProvider;

public void use() {
    B b = bProvider.getObject();   // 每次都拿新实例
}

5. JDK 版本与 javax/jakarta

Spring 6 / Spring Boot 3 把 javax.annotation 改成了 jakarta.annotation——老项目升级时 @Resource 的 import 要批量替换


十一、选型建议

给一个直接的工程建议:

首选构造器注入(用 Lombok 的 @RequiredArgsConstructor),二选 @Autowired + @Qualifier,最后选 @Resource@Resource 在新代码里其实没什么不可替代的优势。


小结

把全文压一句:

@Autowired 优先按类型,@Resource 优先按名字;前者 Spring 私货功能更全,后者 JSR 标准。但两者最佳实践都是『不要用在字段上』——构造器注入才是 Spring 官方推荐的姿势。

记住几点:

  • 字段注入是反模式,无论用哪个注解
  • 构造器注入只能用 @Autowired(Spring 4.3 后构造器只有一个时连注解都不需要)
  • 集合注入用 @Autowired 才够灵活
  • @Resource 在按 Bean name 精确匹配时稍简洁,但不是不可替代

下次写 Service 字段时——先想想"是否能改成构造器”,再选注解。

使用 Hugo 构建
主题 StackJimmy 设计