Spring 依赖注入一次讲透:@Autowired、@Resource、@Qualifier、@Primary 到底怎么选?
很多人会用 Spring 注入,但一到面试或源码分析场景,就容易把
@Autowired、@Resource、构造器注入、@Qualifier、@Primary说乱。这篇文章就把这些最常见、也最容易混淆的问题一次讲透。
摘要
这篇文章主要解决 4 个问题:
@Resource和@Autowired的本质区别是什么?- 字段注入、
setter注入、构造器注入到底怎么选? - 多个同类型 Bean 同时存在时,
@Qualifier和@Primary各起什么作用? - Spring 容器到底是怎么把依赖自动注入进去的?
如果你最近在补 Spring 基础,或者你已经会写 Spring 代码,但总觉得依赖注入这一块讲不清,这篇文章适合你。
适读人群
- 刚接触 Spring,想把依赖注入学扎实的同学
- 工作中常写 Spring Boot,但对底层机制理解还不够清晰的开发
- 准备面试,想把
@Autowired、@Qualifier、构造器注入这些高频问题讲明白的 Java 工程师
目录
- 前言
- [一、
@Resource和@Autowired的区别](#一、@Resource 和 @Autowired 的区别 "#%E4%B8%80resource-%E5%92%8C-autowired-%E7%9A%84%E5%8C%BA%E5%88%AB") - [二、字段注入、Setter 注入、构造器注入怎么选?](#二、字段注入、Setter 注入、构造器注入怎么选? "#%E4%BA%8C%E5%AD%97%E6%AE%B5%E6%B3%A8%E5%85%A5setter-%E6%B3%A8%E5%85%A5%E6%9E%84%E9%80%A0%E5%99%A8%E6%B3%A8%E5%85%A5%E6%80%8E%E4%B9%88%E9%80%89")
- [三、
@Autowired、@Qualifier、@Primary一起怎么理解?](#三、@Autowired、@Qualifier、@Primary 一起怎么理解? "#%E4%B8%89autowiredqualifierprimary-%E4%B8%80%E8%B5%B7%E6%80%8E%E4%B9%88%E7%90%86%E8%A7%A3") - [四、Spring 到底是怎么把 Bean 注进去的?](#四、Spring 到底是怎么把 Bean 注进去的? "#%E5%9B%9Bspring-%E5%88%B0%E5%BA%95%E6%98%AF%E6%80%8E%E4%B9%88%E6%8A%8A-bean-%E6%B3%A8%E8%BF%9B%E5%8E%BB%E7%9A%84")
- 五、为什么字段注入不如构造器注入?
- 六、实战中怎么用最稳?
- 七、面试里怎么回答这几个问题?
- 八、总结
- 写在最后
前言
很多 Java 开发刚接触 Spring 时,最容易混淆的几件事就是:
@Resource和@Autowired到底有什么区别?- 字段注入、
setter注入、构造器注入到底该怎么选? - 多个同类型 Bean 存在时,
@Qualifier和@Primary分别起什么作用? - Spring 容器到底是怎么把 Bean 注进去的?
这些问题单独拆开都不难,但如果没有一条清晰主线,很容易学成"会写、但说不清为什么"。
很多时候,开发者的问题不是不会用,而是:
- 只知道怎么写,不知道为什么这样写
- 代码能跑,但对依赖注入底层过程没概念
- 一到多实现 Bean、构造器注入、面试深挖场景就容易混乱
这篇文章就把这些内容串起来,用一条主线讲清楚 Spring 依赖注入里最常见、也最容易混淆的几个点。
一、@Resource 和 @Autowired 的区别
先说结论:
@Autowired:默认按 类型 注入,属于 Spring 自己的注解@Resource:默认先按 名称,再按类型注入,属于 Java 标准注解
1. 来源不同
@Autowired 来自 Spring:
java
import org.springframework.beans.factory.annotation.Autowired;
@Resource 来自 JSR-250 标准:
java
import javax.annotation.Resource;
或者在新版本里:
java
import jakarta.annotation.Resource;
2. 默认注入规则不同
@Autowired
默认按类型找 Bean。
java
@Autowired
private UserService userService;
Spring 会优先找类型为 UserService 的 Bean。
@Resource
默认先按名称找,再按类型找。
java
@Resource
private UserService userService;
Spring 会优先找名字叫 userService 的 Bean;找不到时,再尝试按类型匹配。
3. 多个同类型 Bean 时的表现不同
假设有两个实现:
java
public interface UserService {
void login();
}
java
@Service("userServiceA")
public class UserServiceA implements UserService {
@Override
public void login() {
System.out.println("A");
}
}
java
@Service("userServiceB")
public class UserServiceB implements UserService {
@Override
public void login() {
System.out.println("B");
}
}
这时:
java
@Autowired
private UserService userService;
会报错,因为按类型找到多个 Bean,不知道该注入哪一个。
而 @Resource 如果字段名恰好和 Bean 名一致,就可能直接命中:
java
@Resource(name = "userServiceA")
private UserService userService;
4. 怎么选?
如果你在现代 Spring 项目里开发,建议这样选:
- 优先使用 构造器注入
- 需要自动按类型注入时,用
@Autowired - 需要显式指定 Bean 名称时,用
@Qualifier - 老项目里出现
@Resource很正常,但新代码不建议大量混用
二、字段注入、Setter 注入、构造器注入怎么选?
这三种方式本质上都是依赖注入,只是"把依赖塞进类里"的方式不同。
1. 字段注入
java
@Service
public class OrderService {
@Autowired
private UserService userService;
}
优点
- 写法最简单
- 代码最短
缺点
- 依赖是隐藏的,看类定义时不够直观
- 不方便单元测试
- 不适合
final字段 - 对象是先创建,再通过反射注入,约束不强
结论
字段注入能用,但不推荐作为新项目的首选方案。
2. Setter 注入
java
@Service
public class OrderService {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
优点
- 比字段注入更显式一些
- 适合"可选依赖"或运行时可能替换的依赖
缺点
- 依赖不是强制的
- 对象可以先创建,再晚些时候才完整
- 一般不适合作为主流方案
适用场景
- 某个依赖是可选的
- 某个依赖后续可能变更
3. 构造器注入
java
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
}
}
优点
- 依赖关系最清晰
- 可以配合
final - 对象一创建就处于完整可用状态
- 最方便做单元测试
- 约束最强,最符合设计原则
为什么推荐构造器注入?
因为一个类如果离不开某个依赖,那它在创建时就应该拿到这个依赖,而不是先造一个半成品对象,再由框架补进去。
这也是为什么现代 Spring 开发里,构造器注入是首选方案。
Spring 里的现代写法
如果类中只有一个构造器,Spring 4.3+ 以后连 @Autowired 都可以省略:
java
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
}
}
如果你使用 Lombok,也可以写成:
java
@Service
@RequiredArgsConstructor
public class OrderService {
private final UserService userService;
}
结论
推荐顺序如下:
- 构造器注入
setter注入- 字段注入
三、@Autowired、@Qualifier、@Primary 一起怎么理解?
这是 Spring 中多实现 Bean 场景下最常见的组合。
先记住一句话:
@Autowired:先按类型找@Qualifier:从多个候选者里指定一个@Primary:多个候选者存在时,设置默认优先 Bean
1. 只有 @Autowired
java
@Autowired
private PayService payService;
如果容器里只有一个 PayService 实现,没问题。
如果有多个,就会报错。
2. @Qualifier
当同类型 Bean 有多个时,你可以显式指定注入哪个 Bean。
java
public interface PayService {
void pay();
}
java
@Service("aliPayService")
public class AliPayService implements PayService {
@Override
public void pay() {
System.out.println("AliPay");
}
}
java
@Service("wechatPayService")
public class WechatPayService implements PayService {
@Override
public void pay() {
System.out.println("WechatPay");
}
}
使用方式:
java
@Autowired
@Qualifier("wechatPayService")
private PayService payService;
这表示:
按类型找到多个
PayService后,使用名字为wechatPayService的那个。
3. @Primary
如果某个 Bean 是"默认实现",可以把它标成 @Primary。
java
@Service
@Primary
public class AliPayService implements PayService {
@Override
public void pay() {
System.out.println("AliPay");
}
}
java
@Service
public class WechatPayService implements PayService {
@Override
public void pay() {
System.out.println("WechatPay");
}
}
这时:
java
@Autowired
private PayService payService;
默认会注入 AliPayService。
4. 优先级关系
通常可以这样理解:
- 先按类型找 Bean
- 如果只有一个,直接注入
- 如果有多个:
- 有
@Qualifier,优先按@Qualifier指定 - 没有
@Qualifier,看有没有@Primary - 如果还不唯一,就报错
- 有
也就是说:
@Qualifier的优先级高于@Primary
5. 推荐写法
建议和构造器注入一起使用:
java
@Service
public class OrderFacade {
private final PayService payService;
public OrderFacade(@Qualifier("wechatPayService") PayService payService) {
this.payService = payService;
}
}
这样依赖最清晰,也更适合测试。
四、Spring 到底是怎么把 Bean 注进去的?
很多人会用 @Autowired,但对它背后的机制没什么概念。其实它的核心过程并不复杂。
你可以把它理解成这样一句话:
Spring 在创建 Bean 的过程中,发现这里缺一个对象,就去容器里找一个合适的 Bean 塞进去。
下面按流程拆开讲。
1. 扫描并注册 BeanDefinition
Spring 启动时,会先扫描这些注解修饰的类:
@Component@Service@Controller@Repository@Configuration
这些类会先被注册成 BeanDefinition。
注意,这一步只是"把 Bean 的定义信息收集起来",还不是所有对象都已经实例化。
2. 创建 Bean 实例
假设有这样一个类:
java
@Service
public class OrderService {
@Autowired
private UserService userService;
}
Spring 在创建 OrderService 时:
- 如果是构造器注入,会先解析构造器参数
- 如果是字段注入,会先创建对象,再注入字段
也就是说,创建 Bean 和注入依赖是两个阶段。
3. 解析依赖
Spring 看到这个字段:
java
@Autowired
private UserService userService;
会开始做依赖解析。
它会问自己:
- 这里需要一个什么类型的 Bean?
- 容器里有没有这种类型的 Bean?
- 如果有多个,该选哪个?
这时就会结合:
- 类型匹配
@Qualifier@Primary
来决定最终注入哪个对象。
4. 执行注入
找到目标 Bean 后,Spring 会把依赖注入进去。
不同注入方式的本质分别是:
字段注入
通过反射给字段赋值。
本质类似:
java
orderService.userService = 容器里找到的那个Bean;
只是这个动作是由 Spring 通过反射完成的。
Setter 注入
本质是调用 setter 方法:
java
orderService.setUserService(bean);
构造器注入
本质是在创建对象时把依赖作为参数传进去:
java
new OrderService(bean)
5. 哪个类在处理 @Autowired?
最关键的一个类是:
AutowiredAnnotationBeanPostProcessor
它的作用可以简单理解为:
专门负责处理
@Autowired、@Value、@Inject等注解的 Bean 后置处理器。
它会在 Bean 创建后,检查:
- 哪些字段需要注入
- 哪些方法需要注入
- 哪些构造器可以用来注入
然后驱动 Spring 完成依赖注入。
6. 一个简化版流程图
你可以把 Spring 依赖注入的主流程简化成下面这样:
- 扫描类
- 注册
BeanDefinition - 创建 Bean
- 解析依赖
- 执行注入
- 调用初始化方法
- 放入单例池
五、为什么字段注入不如构造器注入?
这个问题其实是很多人从"会用 Spring"走向"理解设计"的关键一步。
字段注入的问题
字段注入是这样一种模型:
- 先创建一个对象
- 再通过反射把依赖补进去
这意味着对象在创建瞬间可能是不完整的。
构造器注入的优势
构造器注入要求:
只要对象能创建出来,它的依赖就一定是齐全的
所以:
- 结构更清晰
- 更适合
final - 更利于测试
- 更符合面向对象设计原则
如果你把 Spring 依赖注入当成一种工程约束而不是语法糖,就会发现构造器注入几乎总是更合理的选择。
六、实战中怎么用最稳?
如果你想在项目里保持代码干净、可维护,建议遵循下面这套规则:
1. 默认使用构造器注入
java
@Service
public class UserFacade {
private final UserService userService;
private final OrderService orderService;
public UserFacade(UserService userService, OrderService orderService) {
this.userService = userService;
this.orderService = orderService;
}
}
2. 多实现 Bean 时优先用 @Qualifier
java
public UserFacade(@Qualifier("userServiceV2") UserService userService) {
this.userService = userService;
}
3. 如果某个实现是默认实现,再考虑 @Primary
java
@Service
@Primary
public class DefaultUserService implements UserService {
}
4. 字段注入尽量少用
尤其是在新代码里,不建议继续把字段注入当默认写法。
5. @Resource 和 @Autowired 不要随意混用
如果项目没有历史包袱,统一风格更重要。
现代 Spring 项目里,一般更建议:
- 构造器注入
@Qualifier- 必要时使用
@Primary
七、面试里怎么回答这几个问题?
如果面试官问你:
问题 1:@Resource 和 @Autowired 的区别?
你可以简洁回答:
@Autowired是 Spring 注解,默认按类型注入;@Resource是标准注解,默认先按名称再按类型注入。现代 Spring 开发中通常更推荐构造器注入配合@Autowired体系使用。
问题 2:为什么推荐构造器注入?
你可以回答:
因为构造器注入让依赖显式化,支持
final,对象在创建时就完整可用,也更方便单元测试。字段注入虽然写法简单,但依赖隐藏、约束弱、对测试不友好。
问题 3:@Qualifier 和 @Primary 的区别?
你可以回答:
@Primary是默认候选者,@Qualifier是显式指定。多个同类型 Bean 存在时,如果写了@Qualifier,优先按@Qualifier注入;否则看有没有@Primary。
问题 4:Spring 是怎么完成 @Autowired 注入的?
你可以回答:
Spring 启动时先扫描并注册 BeanDefinition,创建 Bean 时通过依赖解析找到匹配的 Bean,然后由
AutowiredAnnotationBeanPostProcessor等机制完成字段、方法或构造器的依赖注入。
八、总结
最后把整篇文章压缩成 4 句话:
@Autowired主要按类型注入,@Resource主要按名称注入- 三种注入方式里,构造器注入最推荐
- 多实现 Bean 场景下,
@Qualifier比@Primary更明确 @Autowired的本质,是 Spring 在创建 Bean 时完成依赖解析和注入