一、两种写法先摆出来
写法 1:传统 @Autowired 字段注入
java
@RestController
public class SysUserController {
@Autowired
private ISysUserService userService;
@Autowired
private ISysRoleService roleService;
}
写法 2:@RequiredArgsConstructor + final 构造注入
java
@RequiredArgsConstructor
@RestController
public class SysUserController {
private final ISysUserService userService;
private final ISysRoleService roleService;
}
二、为什么 Spring 官方不推荐 @Autowired 字段注入?
1. 循环依赖风险更高
比如 A 依赖 B,B 依赖 A:
- 字段注入:容易隐藏循环依赖问题,运行时才报错
- 构造器注入:项目启动直接报错,提早暴露问题,好排查
2. 违反单一职责原则
字段注入很容易无脑往上堆 @Autowired,一个 Controller 注入十几个 Service,类臃肿、职责混乱。构造器注入参数太多,编译器看着就别扭,倒逼你拆分类。
3. 容易造成可变依赖、空指针
字段不是 final,后续可以随便改赋值:
java
userService = null; // 随时能篡改
后面调用直接 NPE。
用 final + 构造注入:一旦初始化完成,永远不能被修改,更安全。
4. 单元测试难
字段注入:必须用 Spring 容器、@MockBean 才能测,脱离容器不好 new 对象。
构造器注入:
java
// 普通 new 就能单元测试,不用启Spring容器
new SysUserController(mockUserService, mockRoleService);
纯 Java 就能测试,轻便很多。
5. 依赖不透明
字段注入藏在类里面,外人看不出这个类到底依赖哪些组件 。构造器参数一目了然:参数就是所有依赖。
三、@RequiredArgsConstructor + final 优势
- 省去手写构造方法,代码极简
- 天然 final 不可变,杜绝篡改和空指针
- Spring 4.3+ 首选推荐,官方标准写法
- 方便单元测试,可手动 new 传参
- 强制依赖明确,不会乱注入一堆组件
四、一句话选型规则
-
业务 Controller / Service / Mapper :统一用
@RequiredArgsConstructor+private final 依赖彻底不用 @Autowired -
只有特殊场景才偶尔用 @Autowired:静态变量注入、特殊工具类、少量非常规 Bean 注入。
五、普通类举例
为什么 SysUserImportListener 必须手写构造方法?
java
@Slf4j
public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo> {
private final ISysUserService userService;
private final String password;
private final Boolean isUpdateSupport;
private final Long operUserId;
// 必须手写!
public SysUserImportListener(Boolean isUpdateSupport) {
String initPassword = SpringUtils.getBean(ISysConfigService.class).selectConfigByKey(...);
this.userService = SpringUtils.getBean(ISysUserService.class);
this.password = BCrypt.hashpw(initPassword);
this.isUpdateSupport = isUpdateSupport;
this.operUserId = LoginHelper.getUserId();
}
}
这里有 3 个 lombok 无法处理的原因:
① 构造方法的参数不匹配
@RequiredArgsConstructor 只会生成 包含所有 final 字段的构造器:
java
// lombok 想生成的是这种(4个参数)
public SysUserImportListener(ISysUserService userService,
String password,
Boolean isUpdateSupport,
Long operUserId) {
}
但你实际需要的构造器只有 1 个参数:
java
public SysUserImportListener(Boolean isUpdateSupport) {
}
② 很多值不是外部传入,而是内部手动获取
- password → 内部查配置生成
- userService → 内部 SpringUtils 获取
- operUserId → 内部 LoginHelper 获取
lombok 不知道这些逻辑,它只会无脑赋值。
java
// 不能用 @RequiredArgsConstructor
private final String password;
private final Boolean isUpdateSupport;
这些值不是 Spring 注入的,是构造里自己:
- 从配置取值
- 加密处理
- 获取登录用户 ID
Lombok 只会帮你按参数直接赋值 ,做不了业务逻辑,所以只能手写构造方法。
③ 这个类不是 Spring Bean
@RestController是 Spring 管理的SysUserImportListener是 EasyExcel 手动 new 出来的对象
java
new SysUserImportListener(true); // 你自己创建的
Spring 不会帮你注入,所以必须自己写构造方法自己处理。
六、一句话终极总结
- Controller + @RequiredArgsConstructor + final 注入 → 不用写构造方法,lombok 自动生成。
- 普通类 + 自己 new + 内部动态赋值 → 必须手写构造方法,lombok 无法处理你的业务逻辑。
总结
@RequiredArgsConstructor只负责:给所有final字段生成全参构造。- 你的监听器 构造参数数量不对 + 内部有业务逻辑 + 不是 SpringBean → 必须手写构造。
- Controller 是标准 Spring 构造注入 → 不用写。