老生常谈但很少人答得明白的一题
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.annotation | javax.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 的查找顺序
flowchart TD
A[按类型查找] --> B{找到 1 个?}
B -->|是| C[注入]
B -->|否,找到 0 个| D{required=true?}
B -->|否,找到多个| E[尝试按名称匹配]
D -->|是| F[抛 NoSuchBeanDefinitionException]
D -->|否| G[注入 null]
E --> H{匹配成功?}
H -->|是| C
H -->|否| I[抛 NoUniqueBeanDefinitionException]简化:先按类型,类型多个时退化到按名字。
@Resource 的查找顺序
flowchart TD
Start[开始] --> Q1{显式指定 name?}
Q1 -->|是| ByName[只按 name 查]
Q1 -->|否| Q2{显式指定 type?}
Q2 -->|是| ByType[只按 type 查]
Q2 -->|否| ByField[默认: 字段名作为 name 查]
ByField --> Q3{找到?}
Q3 -->|否| Fallback[退化到按类型查]
Q3 -->|是| Done[注入]
ByName --> Done
ByType --> Done
Fallback --> Done简化:先按名字(字段名),找不到再按类型。
行为差异举例
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 选项
@Autowired 有 required 属性:
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 默认 |
|---|
@Autowired | Spring | true |
@Resource | JSR-250 | true |
@Inject | JSR-330 | true |
@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 要批量替换。
十一、选型建议
flowchart TD
Start([需要注入依赖?])
Start --> Type{字段还是构造器?}
Type -->|构造器| C[必须 @Autowired
或 @RequiredArgsConstructor]
Type -->|字段,不可改| F{需要按名字精确匹配?}
F -->|是| R[@Resource - 简洁]
F -->|否| A[@Autowired - 类型 + Qualifier]给一个直接的工程建议:
首选构造器注入(用 Lombok 的 @RequiredArgsConstructor),二选 @Autowired + @Qualifier,最后选 @Resource。@Resource 在新代码里其实没什么不可替代的优势。
小结
把全文压一句:
@Autowired 优先按类型,@Resource 优先按名字;前者 Spring 私货功能更全,后者 JSR 标准。但两者最佳实践都是『不要用在字段上』——构造器注入才是 Spring 官方推荐的姿势。
记住几点:
- 字段注入是反模式,无论用哪个注解
- 构造器注入只能用
@Autowired(Spring 4.3 后构造器只有一个时连注解都不需要) - 集合注入用
@Autowired 才够灵活 @Resource 在按 Bean name 精确匹配时稍简洁,但不是不可替代
下次写 Service 字段时——先想想"是否能改成构造器”,再选注解。