首先展示一段在系统中很常见的代码,也算是一种固定写法
@Slf4j
@RequiredArgsConstructor
@Service
@Transactional
@DS(DsConstant.BASE_TENANT)
public class BaseHighlightTagServiceImpl extends SuperServiceImpl<BaseHighlightTagManager, Long, BaseHighlightTag, BaseHighlightTagSaveVO,
BaseHighlightTagUpdateVO, BaseHighlightTagPageQuery, BaseHighlightTagResultVO> implements BaseHighlightTagService {
//在项目启动的时候会将容器中所有的 HighlightTagProcessor 类型的 bean 放到这个 map 中
private final Map<String, HighlightTagProcessor> beanNameMatchBean;
这里有一个疑问
使用构造方法创建bean的时候,@AllArgsConstructor和@RequiredArgsConstructor 有什么不同
在 Spring 中使用 @AllArgsConstructor
和 @RequiredArgsConstructor
生成构造方法时,主要区别在于 生成的构造方法的参数范围不同,具体差异如下:
1. @AllArgsConstructor
(全参构造方法)
- 作用:为类中 所有非静态成员变量(包括
final
和非final
)生成一个全参数的构造方法。 - 适用场景:适用于需要注入所有字段的情况(无论是否必需)。
- 示例:
生成的构造方法:@AllArgsConstructor // 生成包含所有字段的构造方法 @Service public class UserService { private final UserRepository userRepo; // final 字段 private String cacheType; // 非 final 字段 }
public UserService(UserRepository userRepo, String cacheType) { this.userRepo = userRepo; this.cacheType = cacheType; }
2. @RequiredArgsConstructor
(必需参数构造方法)
- 作用:仅为类中 所有
final
字段或标有@NonNull
的字段 生成构造方法。 - 适用场景:适用于仅需注入必需依赖(如
final
字段)的情况,非必需字段可通过 Setter 或直接赋值。 - 示例:
生成的构造方法:@RequiredArgsConstructor // 仅生成 final 字段的构造方法 @Service public class UserService { private final UserRepository userRepo; // final 字段(会被包含) private String cacheType; // 非 final 字段(不会包含) }
public UserService(UserRepository userRepo) { this.userRepo = userRepo; }
✅ 关键区别总结
特性 | @AllArgsConstructor | @RequiredArgsConstructor |
---|---|---|
参数范围 | 所有非静态字段(包括 final 和非 final ) | 仅 final 或 @NonNull 字段 |
适用场景 | 需要强制注入所有字段 | 仅需注入必需依赖(推荐在 Spring 中使用) |
代码简洁性 | 可能包含不必要的参数 | 更精准,避免冗余依赖 |
与 Spring 的配合 | 可能注入非必需字段 | 更符合依赖注入的最佳实践 |
🚀 在 Spring 中的推荐选择
-
优先用
@RequiredArgsConstructor
:
大多数情况下,Spring 依赖注入只需要处理final
字段(必需依赖),非final
字段可以通过其他方式(如 Setter 或默认值)设置。避免全参构造方法引入不必要的复杂度。 -
慎用
@AllArgsConstructor
:
如果类中有非final
字段且需要通过构造方法注入(较少见),才使用它。否则可能导致 Spring 注入不需要的依赖。
📌 最终结论
@RequiredArgsConstructor
是 Spring 项目中的更佳选择,尤其适合依赖注入场景。@AllArgsConstructor
更适合 POJO 或需要全字段初始化的场景(如 DTO、Entity)。
示例:在 Spring Service 中推荐写法:
@RequiredArgsConstructor // 仅注入必需依赖
@Service
public class OrderService {
private final OrderRepository orderRepo; // 必需依赖
private String logPrefix = "Order"; // 非必需,可通过其他方式设置
}
@RequiredArgsConstructor 和 @Autowired 的区别
功能定位不同
@RequiredArgsConstructor
是 Lombok 提供的注解,用于自动生成包含 final
字段或标记为 @NonNull
的字段的构造方法。@Autowired
是 Spring 框架的注解,用于实现依赖注入(DI),自动装配 Bean。
使用场景
@RequiredArgsConstructor
适用于简化代码,减少手动编写构造方法的重复工作,尤其适合需要强制依赖的类(如通过构造方法注入的 final
字段)。@Autowired
用于标记需要由 Spring 容器自动注入的依赖项,可作用于字段、构造方法或方法。
依赖注入方式
@RequiredArgsConstructor
生成的构造方法需要与 Spring 的依赖注入机制配合使用(例如结合 @Autowired
或 Spring 的构造方法注入)。@Autowired
直接声明依赖注入的入口,支持多种注入方式(如字段注入、构造方法注入、Setter 注入)。
代码示例对比
使用 @RequiredArgsConstructor
的 Lombok 方式:
@RequiredArgsConstructor
@Service
public class MyService {
private final MyRepository repository; // 自动生成构造方法
}
使用 @Autowired
的 Spring 方式:
@Service
public class MyService {
@Autowired
private MyRepository repository; // 字段注入
}
关键差异
@RequiredArgsConstructor
是编译时生成的代码,不依赖运行时框架;@Autowired
是 Spring 的运行时行为。@RequiredArgsConstructor
更适用于构造方法注入,而@Autowired
支持多种注入方式。- 若使用构造方法注入,
@Autowired
在 Spring 4.3 后可以省略(单构造方法时),而@RequiredArgsConstructor
仍需显式标注。
二者都有优劣,为什么在 spring 的官方文档中,明确表示 推荐使用构造方法创建bean,而不是@Autowired
1. 构造方法注入的优势
- 不可变性和线程安全:构造方法注入允许bean在创建时就接收所有必需的依赖,这使得bean的状态不可变(即依赖一旦注入就不能被修改)。这有助于避免并发问题,提高线程安全性。例如:
- 如果bean依赖一个服务,通过构造方法注入后,该服务引用在bean生命周期内保持不变。
- 明确依赖关系:构造方法强制所有必需依赖在对象初始化时提供,这确保了bean在创建后就是"完整"的(即所有依赖都已满足)。这减少了运行时错误(如
NullPointerException
),因为Spring容器在bean实例化时会验证依赖是否可用。 - 更好的测试性:在单元测试中,构造方法注入使依赖注入更简单。测试时可以直接通过构造方法传递mock对象,而不需要依赖Spring容器或反射。例如:
// 生产中:Spring自动注入 public class MyService { private final Dependency dep; public MyService(Dependency dep) { this.dep = dep; } } // 测试中:手动注入mock Dependency mockDep = mock(Dependency.class); MyService service = new MyService(mockDep);
- 避免循环依赖:Spring在启动时处理依赖关系,构造方法注入能更早地暴露循环依赖问题(例如,如果A依赖B,B又依赖A,构造方法注入会立即失败),而setter或字段注入可能延迟问题到运行时。
- 符合现代设计原则:构造方法注入鼓励使用final字段(在Java中),这强制依赖在编译时就被声明,符合不可变对象和函数式编程的最佳实践。
2. @Autowired
(特别是字段注入)的劣势
虽然@Autowired
可以用于构造方法(这时它与构造方法注入等效),但官方文档不推荐将其用于字段注入(即在字段上直接添加@Autowired
)。原因包括:
- 依赖不明确:字段注入使依赖关系隐式化。bean可以在没有所有必需依赖的情况下被创建(如果字段是
@Autowired(required=false)
),这可能导致bean处于不完整状态,容易引发运行时错误。 - 测试困难:字段注入依赖于Spring容器或反射机制来设置依赖。在单元测试中,必须使用工具(如
ReflectionTestUtils
)手动注入mock,这增加了测试复杂性和维护成本。例如:// 字段注入示例(不推荐) public class MyService { @Autowired private Dependency dep; } // 测试中需要反射 MyService service = new MyService(); ReflectionTestUtils.setField(service, "dep", mockDep); // 繁琐且易错
- 违反单一职责原则:字段注入可能鼓励bean拥有过多依赖(因为添加新字段太容易),这可能导致类膨胀和耦合度增加。而构造方法注入通过参数列表显式暴露依赖数量,促使开发者思考类的职责边界。
- 潜在的空指针风险:如果Spring容器配置错误或依赖未找到,字段注入的bean可能在访问依赖时抛出
NullPointerException
,而构造方法注入在启动时就会失败,提供更早的错误反馈。 - 循环依赖处理问题:对于字段注入,Spring可能使用"三级缓存"机制延迟解决循环依赖,但这可能掩盖设计缺陷,导致应用在运行时出现不可预测行为。
3. 为什么Spring官方推荐构造方法注入?
- 核心哲学:Spring强调"显式优于隐式"和"不可变性"的设计理念。构造方法注入使依赖关系在代码中一目了然,符合Spring的"约定优于配置"原则。
- 文档依据:在Spring官方文档(如Core Technologies部分)中,明确指出:
- 构造方法注入是注入必需依赖的首选方式,因为它确保bean不可变和完全初始化。
@Autowired
可用于可选依赖或setter注入,但应避免用于字段注入。
- 版本演进:从Spring 4.3开始,如果类只有一个构造方法,Spring会自动将其用于注入,无需显式添加
@Autowired
注解,这进一步简化了构造方法注入的使用。而在Spring 5.x中,推荐做法更偏向构造方法。 - 平衡点:对于可选依赖,Spring文档建议使用setter注入(结合
@Autowired
),但必需依赖应优先通过构造方法处理。这保持了灵活性,同时最小化风险。
总结
Spring官方推荐构造方法创建bean,主要是因为它在不可变性、安全性和可测试性上优于@Autowired
字段注入。构造方法注入强制明确依赖关系,减少运行时错误,并促进更健壮的代码设计。而@Autowired
字段注入虽然语法简洁,但容易引入隐藏问题,因此应谨慎使用(仅在构造方法或setter注入不适合时)。在实际开发中,遵循这一推荐可以提升应用的稳定性和可维护性。