在 Spring 依赖注入(Dependency Injection, DI) 中,有三种主要的方式:
1. 构造函数注入(Constructor Injection)
2. Setter 方法注入(Setter Injection)
3. 字段注入(Field Injection)
1. 构造函数注入(Constructor Injection) ✅ 推荐方式
原理:通过构造函数为 Bean 传递依赖,在对象创建时就完成依赖注入。
优点:
• 不可变性:依赖是 final 的,保证依赖不会被修改,符合 SOLID 原则(SRP, DI)。
• 强制依赖注入:创建对象时就提供了所有必要的依赖,避免了空指针问题。
• 更方便单元测试:可以通过构造函数直接提供 mock 依赖。
示例:
@Component
public class UserService {
private final UserRepository userRepository;
@Autowired // Spring 4.3+ 可省略
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void registerUser(String name) {
userRepository.save(name);
}
}
最佳实践:推荐使用构造函数注入,并且让依赖 final,确保不可变。
2. Setter 方法注入(Setter Injection)
原理:Spring 在创建对象后,通过Setter 方法注入依赖。
优点:
• 适用于可选依赖(非必须的依赖)。
• 可以在运行时动态修改依赖。
缺点:
• 依赖可能是 null,导致 NullPointerException。
• 破坏不可变性(对象在创建后依然可以被修改)。
• 依赖不明确,可能创建出**半初始化(partially initialized)**对象。
示例:
@Component
public class UserService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
适用场景:如果某些依赖是可选的,可以使用 Setter 注入,但构造函数注入仍是更好的选择。
3. 字段注入(Field Injection) ❌ 不推荐
原理:Spring 直接在字段上使用 @Autowired,无需构造函数或 Setter 方法。
优点:
• 代码更简洁,省去了构造函数和 Setter 方法。
缺点:
• 破坏封装性,外部无法控制依赖,难以进行单元测试(无法直接 mock 依赖)。
• 依赖是隐藏的,违反 DI 原则,可维护性变差。
• 不能使用 final 关键字,导致对象可以被随意修改。
示例:
@Component
public class UserService {
@Autowired
private UserRepository userRepository;
}
避免使用:字段注入适合临时/实验代码,但在生产环境推荐构造函数注入。
三者对比总结
方式
是否推荐
优点
缺点
适用场景
构造函数注入
✅ 强烈推荐
依赖不可变、强制依赖注入、利于测试
代码略长
推荐默认使用
Setter 方法注入
⚠️ 次选
适用于可选依赖
破坏不可变性、可能为空
依赖可选或需要修改时
字段注入
❌ 不推荐
代码最简洁
破坏封装、难以测试、依赖隐藏
临时测试或实验代码
最佳实践
• 默认使用 构造函数注入,并让依赖 final(保证不可变)。
• Setter 注入仅适用于可选依赖,如日志、配置等。
• 避免使用字段注入,特别是测试驱动开发(TDD)时。
如果你的代码需要可测试性、可维护性和更好的设计,构造函数注入是最佳选择! 🚀