Spring 依赖注入一次讲透:`@Autowired`、`@Resource`、`@Qualifier`、`@Primary` 到底怎么选

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;
}

结论

推荐顺序如下:

  1. 构造器注入
  2. setter 注入
  3. 字段注入

三、@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. 优先级关系

通常可以这样理解:

  1. 先按类型找 Bean
  2. 如果只有一个,直接注入
  3. 如果有多个:
    • @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;

会开始做依赖解析。

它会问自己:

  1. 这里需要一个什么类型的 Bean?
  2. 容器里有没有这种类型的 Bean?
  3. 如果有多个,该选哪个?

这时就会结合:

  • 类型匹配
  • @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 依赖注入的主流程简化成下面这样:

  1. 扫描类
  2. 注册 BeanDefinition
  3. 创建 Bean
  4. 解析依赖
  5. 执行注入
  6. 调用初始化方法
  7. 放入单例池

五、为什么字段注入不如构造器注入?

这个问题其实是很多人从"会用 Spring"走向"理解设计"的关键一步。

字段注入的问题

字段注入是这样一种模型:

  1. 先创建一个对象
  2. 再通过反射把依赖补进去

这意味着对象在创建瞬间可能是不完整的。

构造器注入的优势

构造器注入要求:

只要对象能创建出来,它的依赖就一定是齐全的

所以:

  • 结构更清晰
  • 更适合 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 句话:

  1. @Autowired 主要按类型注入,@Resource 主要按名称注入
  2. 三种注入方式里,构造器注入最推荐
  3. 多实现 Bean 场景下,@Qualifier@Primary 更明确
  4. @Autowired 的本质,是 Spring 在创建 Bean 时完成依赖解析和注入
相关推荐
Rsun045512 小时前
16、Java 迭代器模式从入门到实战
java·开发语言·迭代器模式
quan26312 小时前
20260416,日常开发-再记一次内存溢出
java·内存溢出·jprofile
布吉岛的石头2 小时前
线上服务凌晨OOM:一次因「无超时设置」引发的内存雪崩复盘
java
SamDeepThinking2 小时前
Spring Bean作用域的设计与使用
java·后端·面试
Flittly2 小时前
【SpringSecurity新手村系列】(2)整合 MyBatis 实现数据库认证
java·安全·spring·springboot·安全架构
前端一课2 小时前
《NestJS 从入门到资深》书稿(Markdown)
后端
Memory_荒年2 小时前
Java + FFmpeg:从“玩具”到“工业级”的音视频实战
后端
Oliver_LaVine2 小时前
java项目启动报错:CreateProcess error=206, 文件名或扩展名太长
java·linux·jenkins