Spring三大注入注解深度拆解:@Autowired、@Resource、@RequiredArgsConstructor 原理、示例、场景选型、面试全解

Spring三大注入注解深度拆解:@Autowired、@Resource、@RequiredArgsConstructor 原理、示例、场景选型、面试全解

前言

做 Spring 开发,依赖注入 DI 是每日必写代码,90% 开发者都会混淆三个高频注解:@Autowired@Resource@RequiredArgsConstructor

很多人只会机械复制代码,分不清三者出身、匹配规则、优缺点,面试连环追问直接卡壳;项目混用多种注入方式,代码规范混乱、循环依赖、空指针、多实现 Bean 启动报错层出不穷。

本文一次性讲透:三者底层原理、完整可运行示例、相同点、核心差异、分场景选型规范、高频踩坑、面试标准答案,适合收藏作为团队代码规范参考。

一、基础总览:一句话分清三者定位

注解 所属来源 核心本质 核心作用
@Autowired Spring 框架专属 注入标记注解 标记字段 / 构造 /setter,Spring 自动装配 Bean,默认按类型匹配
@Resource JSR-250 Java 官方标准 注入标记注解 Java 规范注解,跨容器兼容,优先按 Bean 名称匹配
@RequiredArgsConstructor Lombok 工具类 代码生成语法糖 编译期自动生成 final/@NonNull 字段构造,本身不具备注入能力,配合 Spring 构造注入简化代码

核心共识:三者最终目标统一 ------ 交给 Spring IoC 容器管理依赖,无需手动 new 对象,解耦业务代码。

二、逐个详解:源码示例、底层规则、使用场景

2.1 @Autowired(Spring 原生注入注解)

1)依赖包
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
2)底层匹配规则
  1. 默认byType(按类类型) 优先匹配容器 Bean;

  2. 同接口多个实现类时报错,搭配@Qualifier("beanName")指定名称区分;

  3. 支持required = true/false

    • required=true(默认):找不到 Bean 直接抛异常,项目启动失败;

    • required=false:找不到依赖不报错,字段为 null;

  4. 可标注位置:字段、构造方法、setter 方法、普通业务方法、方法参数。

3)完整可运行三种写法示例
① 字段注入(简单但 Spring 不推荐生产)
java 复制代码
@Service
public class UserService {
    // 直接标注字段,Spring自动注入UserMapper
    @Autowired
    private UserMapper userMapper;

    public User getById(Long id) {
        return userMapper.selectById(id);
    }
}

缺点:字段无法加final,对象可被二次修改;单元测试需要反射赋值,无法提前暴露循环依赖。

② 构造方法注入(Spring 官方第一推荐)

Spring4.3 特性:类只有一个构造器 时,可省略@Autowired自动注入

java 复制代码
@Service
public class UserService {
    // final修饰,对象不可变,杜绝空指针
    private final UserMapper userMapper;
    private final RedisTemplate<String, Object> redisTemplate;

    // 构造注入,依赖在对象创建时全部就绪
    @Autowired
    public UserService(UserMapper userMapper, RedisTemplate<String, Object> redisTemplate) {
        this.userMapper = userMapper;
        this.redisTemplate = redisTemplate;
    }
}
③ Setter 注入(仅适合可选依赖)
java 复制代码
@Service
public class UserService {
    private LogClient logClient;

    // 日志客户端非必选组件,找不到不影响项目启动
    @Autowired(required = false)
    public void setLogClient(LogClient logClient) {
        this.logClient = logClient;
    }
}
④ 解决多实现类冲突:搭配 @Qualifier
java 复制代码
// 支付接口,两个实现类:AliPay、WechatPay
public interface PayHandler {}
@Component("aliPay")
public class AliPay implements PayHandler {}
@Component("wechatPay")
public class WechatPay implements PayHandler {}

// 使用@Autowired + @Qualifier指定Bean名称
@Service
public class OrderService {
    @Autowired
    @Qualifier("aliPay")
    private PayHandler payHandler;
}
4)适用场景
  1. 需要可选依赖required=false),组件不存在不阻塞项目启动;

  2. 多实现接口场景,灵活搭配@Qualifier精准指定 Bean;

  3. 需要注入 Spring 底层原生组件(RestTemplate、TransactionManager 等);

  4. 项目禁止引入 Lombok,只能手写构造注入;

  5. 需要 setter 延迟注入、动态替换依赖的场景。

5)缺点
  • 字段注入无法使用final,线程安全性弱;

  • 纯字段注入隐藏依赖关系,代码可读性差;

  • 大量依赖时手写超长构造,代码冗余。

2.2 @Resource(JSR-250 Java 标准注解)

1)依赖包(JDK 内置,无需额外引入)
java 复制代码
import javax.annotation.Resource;
2)底层匹配规则
  1. 优先 byName(按名称匹配),找不到同名 Bean 再降级 byType 按类型匹配;

  2. 支持name属性手动指定 Bean 名称:@Resource(name = "xxx")

  3. required参数,依赖缺失直接启动报错,不支持可选注入;

  4. 限制:不能标注在构造方法上,仅支持字段、setter 方法;

  5. 框架无关:不绑定 Spring,兼容 Jakarta EE、JBoss 等其他 IoC 容器。

3)完整代码示例
① 不指定 name:优先匹配和字段同名 Bean
java 复制代码
@Service
public class UserService {
    // 先找容器中名称=userMapper的Bean,不存在则按UserMapper类型匹配
    @Resource
    private UserMapper userMapper;
}
② 手动指定 Bean 名称,解决多实现冲突
java 复制代码
@Service
public class OrderService {
    // 强制使用名称为wechatPay的实现类
    @Resource(name = "wechatPay")
    private PayHandler payHandler;
}
4)适用场景
  1. 追求 Java 标准,不想强耦合 Spring 专属注解;

  2. 老项目 JavaEE、JNDI 资源兼容场景;

  3. 团队统一规范,习惯通过变量名区分不同实现 Bean;

  4. 未来可能迁移至其他非 Spring 容器的中间件基础层。

5)缺点
  1. 不支持构造注入,无法搭配final实现不可变对象;

  2. 没有可选注入required=false功能;

  3. 多实现类灵活性弱于@Autowired + @Qualifier

  4. 无法标注构造器,不能使用 Spring 最优构造注入方案。

2.3 @RequiredArgsConstructor(Lombok 构造生成注解)

1)前置依赖:必须引入 Lombok

Maven 依赖

xml 复制代码
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <scope>provided</scope>
</dependency>

IDE 必须安装 Lombok 插件,否则编译报错。

2)底层核心原理

Lombok 在编译期 AST 抽象语法树转换,自动生成构造方法,仅纳入两类字段作为构造参数:

  1. 未初始化的private final修饰成员变量;

  2. @lombok.NonNull标记的字段;

    静态变量、普通非 final 字段不会进入构造;

    @NonNull字段自动生成非空判断,传入 null 直接抛出空指针异常。

3)最简标准业务代码(企业主流写法)
java 复制代码
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class UserService {
    // final字段自动生成构造参数,Spring单构造自动注入,无需@Autowired
    private final UserMapper userMapper;
    private final RedisTemplate<String, Object> redisTemplate;
}
编译后等价手写代码(Lombok 自动生成)
java 复制代码
@Service
public class UserService {
    private final UserMapper userMapper;
    private final RedisTemplate<String, Object> redisTemplate;

    // 自动生成全final字段构造
    public UserService(UserMapper userMapper, RedisTemplate<String, Object> redisTemplate) {
        this.userMapper = userMapper;
        this.redisTemplate = redisTemplate;
    }
}
搭配 @NonNull 手动强制非空校验
java 复制代码
@Service
@RequiredArgsConstructor
public class UserService {
    private final UserMapper userMapper;
    @NonNull
    private LogClient logClient;
    // 普通变量,不会进入构造
    private String tempStr;
}

生成构造内自动添加判空代码:

java 复制代码
if (logClient == null) {
    throw new NullPointerException("logClient is marked non-null but is null");
}
4)适用场景(90% 新项目推荐)
  1. Controller、Service、Manager 核心业务层统一规范;

  2. 依赖数量多,不想手写超长构造方法,减少样板代码;

  3. 强制依赖final不可变,规避运行时空指针;

  4. 遵循 Spring 官方构造注入最佳实践,无需重复写@Autowired

  5. 频繁新增 / 删除依赖,构造方法自动同步,无需手动修改。

5)限制与坑
  1. 项目不允许引入 Lombok 时完全无法使用;

  2. 类存在多个手写构造器时,Spring 无法识别注入构造,必须手动给目标构造加@Autowired

  3. 无法实现可选依赖注入(所有 final 依赖强制必须存在);

  4. 静态变量不会参与构造生成。

三、三大注解相同点(面试高频简答题)

  1. 服务统一目标:全部依托 Spring IoC 容器实现依赖自动装配,手动管理对象创建,降低代码耦合;

  2. 使用范围一致 :均用于@Service/@Controller/@Component等 Spring 托管 Bean 内部;

  3. 注入能力互通:均可注入容器内所有 Bean:Mapper、Service、RedisTemplate、配置 Bean、第三方工具类;

  4. 默认强制校验:无特殊配置时,缺失对应 Bean 都会抛出异常,阻止项目正常启动;

  5. 设计思想统一:遵循依赖倒置、面向接口编程,方便单元测试 Mock 替换依赖;

  6. 最终效果一致:注入完成后,类内部可直接调用依赖方法,无需实例化。

四、全方位核心差异对比表(面试背诵版)

对比维度 @Autowired @Resource @RequiredArgsConstructor
来源框架 Spring 专属 JSR-250 Java 标准 Lombok 工具
核心职责 标记依赖,执行注入逻辑 标记依赖,执行注入逻辑 编译生成构造方法,不参与注入
匹配顺序 先 byType,后 byName 先 byName,后 byType 无匹配逻辑,仅提供构造参数
支持构造注入 ✅ 完全支持 ❌ 禁止标注构造 ✅ 间接实现构造注入(主流)
可选依赖 支持 required=false 不支持 不支持,全部强制依赖
final 字段兼容 字段注入无法 final 无法 final 强制搭配 final,推荐写法
多实现解决方案 @Qualifier 灵活指定 name 属性指定,灵活性差 配合 @Autowired+Qualifier 使用
第三方依赖 仅依赖 Spring JDK 内置无依赖 必须引入 Lombok
多容器兼容 仅 Spring 可用 跨 IoC 容器通用 依赖 Spring+Lombok 双环境
空值自动校验 @NonNull 字段自动判空抛 NPE

五、分场景标准选型规范(直接用作团队编码规范)

场景 1:业务 Service、Controller、Manager(首选)

方案:@RequiredArgsConstructor + private final 字段

优势:代码极简、不可变、符合 Spring 官方推荐、无冗余 @Autowired,统一团队风格。

java 复制代码
@RestController
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;
}

场景 2:存在可选依赖(组件可有可无,不阻塞启动)

方案:@Autowired (required = false) 字段 /setter 注入

java 复制代码
@Autowired(required = false)
private MonitorClient monitorClient;

场景 3:接口多个实现类,需要精准控制 Bean

方案:@Autowired + @Qualifier

java 复制代码
@Autowired
@Qualifier("wxPayHandler")
private PayHandler payHandler;

场景 4:项目禁止引入 Lombok

方案:手写构造 + @Autowired

java 复制代码
@Service
public class UserService {
    private final UserMapper userMapper;
    @Autowired
    public UserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }
}

场景 5:兼容 JavaEE 标准、后续可能更换 IoC 容器

方案:@Resource (name="beanName")

java 复制代码
@Resource(name = "dataSource")
private DataSource dataSource;

场景 6:传统 JNDI、老 Web 项目迁移

方案:统一使用 @Resource

六、高频踩坑避坑指南

  1. @RequiredArgsConstructor 与 @NoArgsConstructor 不能同时使用

    同时生成无参 + 有参两个构造,Spring 启动时无法判定使用哪个注入,直接报错。

  2. 手写多个构造方法时,Lombok 生成构造无法自动注入

    解决方案:手动给 Lombok 生成的构造添加@Autowired

  3. @Resource 不能写在构造方法上

    强行标注直接编译报错,想要构造注入只能换另外两种方案。

  4. final 字段不能使用字段注入(@Autowired/@Resource)

    final 字段必须在构造时赋值,字段注入时机晚于构造,编译报错。

  5. 混用字段注入与 @RequiredArgsConstructor,代码风格混乱

    规范:统一全部使用 final+@RequiredArgsConstructor,杜绝零散 @Autowired 字段。

  6. @Autowired 多实现类不添加 @Qualifier,启动抛出 NoUniqueBeanDefinitionException

    同类型多个 Bean,Spring 无法选择注入对象,必须指定区分标识。

七、面试高频问答(背诵版)

1. @Autowired 和 @Resource 区别?

  1. 来源:Autowired 是 Spring,Resource 是 JSR-250 Java 标准;

  2. 匹配顺序:Autowired 按类型优先,Resource 按名称优先;

  3. 支持位置:Autowired 支持构造,Resource 仅字段 /setter;

  4. 可选依赖:Autowired 有 required=false,Resource 无;

  5. 跨容器:Resource 兼容性更强。

2. @RequiredArgsConstructor 作用?和 @Autowired 关系?

@RequiredArgsConstructor 是 Lombok 注解,编译生成 final/@NonNull 字段的构造方法;Spring4.3 单构造无需 @Autowired 自动注入,二者搭配简化构造注入代码;它本身不具备注入能力,依赖 Spring 容器完成装配。

3. Spring 为什么推荐构造注入,不推荐字段注入?

  1. 构造注入可使用 final,对象不可变,避免空指针;

  2. 对象创建时所有依赖必须就绪,提前暴露循环依赖;

  3. 单元测试可直接 new 传参,无需反射注入;

  4. 依赖关系显性化,代码可读性更高。

4. 三个注解推荐使用顺序?

新项目允许 Lombok:@RequiredArgsConstructor > @Autowired > @Resource

禁止 Lombok 项目:构造注入 @Autowired > @Resource

八、全文总结

  1. @Autowired:Spring 全能注入注解,灵活适配可选依赖、多实现类场景,适合各类特殊注入需求;

  2. @Resource:Java 标准规范注解,跨容器兼容,适合老项目、追求框架解耦场景;

  3. @RequiredArgsConstructor:现代 Spring 项目最优解,Lombok 语法糖简化构造注入,业务层统一标准首选;

    开发编码统一规范:业务层全部采用@RequiredArgsConstructor + final构造注入;特殊可选依赖使用@Autowired(required=false);跨容器兼容场景使用@Resource