spring依赖注入主要有两种方式:构造方法注入和属性setter注入。构造方法注入通过构造函数参数强制传入依赖,确保对象创建即完整、不可变,适用于强依赖;setter注入则通过setter方法在对象创建后赋值,灵活性高但存在未初始化风险。

Spring 利用构造方法注入实现强依赖初始化,核心在于把依赖作为构造参数强制传入,确保对象一创建就具备完整、不可变的协作能力。这种方式天然契合“强依赖”语义——该依赖必须存在、不可为空、不该在后续被替换。
构造方法注入如何体现“强依赖”
强依赖指一个类正常运行所必需、且生命周期内不应变更的协作对象。构造方法注入通过以下机制体现这一点:
- 编译期与运行期双重校验:构造函数参数类型明确,IDE 和编译器能提前发现缺失 Bean;Spring 启动时若找不到匹配 Bean,直接抛出 UnsatisfiedDependencyException,拒绝启动,不留下半成品对象。
- 字段 final 修饰支持:依赖通常声明为 private final,构造中赋值后无法再修改,从语言层面锁定不变性,杜绝意外覆盖或空指针风险。
- 无参构造器被禁用:只要定义了带参构造器,Java 默认无参构造器消失;Spring 无法绕过依赖直接 new 实例,强制依赖显式提供。
标准写法与关键细节
使用 @Autowired 注解在构造器上(Spring 4.3+ 单构造器可省略该注解),配合 final 字段是推荐实践:
@Service
public class OrderService {
private final PaymentService paymentService;
private final InventoryService inventoryService;
// Spring 4.3+ 中,单构造器可省略 @Autowired
public OrderService(PaymentService paymentService, InventoryService inventoryService) {
this.paymentService = paymentService;
this.inventoryService = inventoryService;
}
public void placeOrder() {
paymentService.charge();
inventoryService.reserve();
}
}
- 多个依赖按需传入,顺序无关(Spring 按类型自动匹配,类型不唯一时用 @Qualifier 区分)。
- 避免在构造器内调用 this 的其他方法(可能导致 this 提前暴露,破坏初始化完整性)。
- 若依赖本身是 prototype 作用域,每次获取 OrderService 实例时,其构造器注入的 prototype Bean 也会重新创建(符合预期)。
对比 Setter 和字段注入,为什么更适合强依赖
Setter 注入允许对象先创建再设依赖,字段注入甚至允许 null 值暂存——二者都可能产生“部分初始化”的中间状态。而构造方法注入:
- 不存在“已实例化但未注入”的窗口期,对象要么完整可用,要么根本造不出来。
- 单元测试时可直接 new 并传入 mock 对象,无需启动容器,测试逻辑清晰可控。
- 阅读代码时,仅看构造签名就能立刻掌握该类的最小运行契约,无需扫描全类查找 @Autowired 字段。
遇到循环依赖怎么办
构造器注入下,A 依赖 B、B 又依赖 A 会直接启动失败。这不是缺陷,而是设计警示:两个本该解耦的强依赖彼此缠绕,说明职责划分有问题。如确需保留,唯一安全解法是引入 @Lazy:
@Component
public class UserService {
private final RoleService roleService;
public UserService(@Lazy RoleService roleService) { // 延迟加载,打破构造时闭环
this.roleService = roleService;
}
}
注意:@Lazy 仅推迟依赖的实际解析时机,并不改变逻辑耦合;应优先重构拆分或引入中介层。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











