Spring为什么推荐使用构造函数进行依赖注入??
Spring 推荐使用构造函数进行依赖注入,这是基于多年的实践和经验总结。
1. 不可变性(Immutability)
核心优势:对象状态不可变
// 构造函数注入 - 不可变
@Service
public class UserService {private final UserRepository userRepository;private final EmailService emailService;// 依赖在构造时确定,后续不可更改public UserService(UserRepository userRepository, EmailService emailService) {this.userRepository = userRepository;this.emailService = emailService;}
}// Setter注入 - 可变(存在风险)
@Service
public class UserService {private UserRepository userRepository;private EmailService emailService;@Autowiredpublic void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository; // 可能被重复设置}
}
2. 依赖完整性保证
在对象创建时确保所有依赖就绪
// 构造函数注入:编译时就能发现缺失的依赖
@Service
public class OrderService {private final PaymentService paymentService;private final InventoryService inventoryService;// 如果Spring找不到PaymentService bean,启动就会失败public OrderService(PaymentService paymentService, InventoryService inventoryService) {this.paymentService = paymentService;this.inventoryService = inventoryService;}public void createOrder(Order order) {// 使用时不需要检查依赖是否为nullpaymentService.processPayment(order); // 安全调用}
}// 对比:Setter注入可能产生NPE
@Service
public class OrderService {private PaymentService paymentService;@Autowiredpublic void setPaymentService(PaymentService paymentService) {this.paymentService = paymentService;}public void createOrder(Order order) {// 可能忘记调用setter,导致NPEpaymentService.processPayment(order); // 潜在NPE风险}
}
3. 更好的测试体验
构造函数注入便于单元测试
// 使用构造函数注入 - 测试简单
@Service
public class UserService {private final UserRepository userRepository;public UserService(UserRepository userRepository) {this.userRepository = userRepository;}public User findUser(Long id) {return userRepository.findById(id);}
}// 测试代码
class UserServiceTest {@Testvoid testFindUser() {// 可以直接new,不需要Mockito注解UserRepository mockRepo = Mockito.mock(UserRepository.class);UserService userService = new UserService(mockRepo); // 简单明了// 测试逻辑...}
}// Setter注入测试相对繁琐
@Service
public class UserService {private UserRepository userRepository;@Autowiredpublic void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}
}class UserServiceTest {@Testvoid testFindUser() {UserService userService = new UserService(); // 先创建空对象UserRepository mockRepo = Mockito.mock(UserRepository.class);userService.setUserRepository(mockRepo); // 再设置依赖// 更复杂的设置过程}
}
4. 循环依赖检测
构造函数注入能早期发现循环依赖
// 循环依赖示例
@Service
public class ServiceA {private final ServiceB serviceB;public ServiceA(ServiceB serviceB) { // Spring启动时会检测到循环依赖this.serviceB = serviceB;}
}@Service
public class ServiceB {private final ServiceA serviceA;public ServiceB(ServiceA serviceA) { // 启动失败:BeanCurrentlyInCreationExceptionthis.serviceA = serviceA;}
}// 控制台错误信息:
// Error creating bean with name 'serviceA':
// Requested bean is currently in creation: Is there an unresolvable circular reference?
5. 代码质量提升
5.1 明确的对象创建契约
// 清晰的依赖声明
@Service
public class OrderProcessingService {private final OrderValidator validator;private final PaymentGateway paymentGateway;private final EmailNotifier emailNotifier;private final AuditLogger auditLogger;// 构造函数明确表达了创建该对象需要哪些依赖public OrderProcessingService(OrderValidator validator,PaymentGateway paymentGateway,EmailNotifier emailNotifier,AuditLogger auditLogger) {this.validator = validator;this.paymentGateway = paymentGateway;this.emailNotifier = emailNotifier;this.auditLogger = auditLogger;}
}
5.2 避免部分初始化状态
// 构造函数注入确保完全初始化
@Service
public class CompleteService {private final DependencyA depA;private final DependencyB depB;private final DependencyC depC;public CompleteService(DependencyA depA, DependencyB depB, DependencyC depC) {this.depA = depA;this.depB = depB;this.depC = depC;// 对象创建后立即处于可用状态}
}// Setter注入可能导致部分初始化
@Service
public class PartialService {private DependencyA depA;private DependencyB depB;@Autowiredpublic void setDepA(DependencyA depA) { this.depA = depA; }// 忘记添加setDepB方法,导致depB为nullpublic void someMethod() {depA.doSomething(); // OKdepB.doAnother(); // NPE!}
}
6. Spring官方推荐演进
历史背景:
- Spring 4.3之前:需要
@Autowired
注解 - Spring 4.3开始:单个构造函数的类可以省略
@Autowired
- Spring Boot 2.x:全面推荐构造函数注入
// Spring 4.3+ 的简化写法
@Service
public class UserService {private final UserRepository userRepository;// 单个构造函数时,@Autowired可省略public UserService(UserRepository userRepository) {this.userRepository = userRepository;}
}
7. 实际应用场景
7.1 必需依赖 vs 可选依赖
@Service
public class ComplexService {// 必需依赖:通过构造函数注入private final UserRepository userRepository;private final PaymentService paymentService;// 可选依赖:通过Setter注入(较少使用)private CacheManager cacheManager;// 必需依赖public ComplexService(UserRepository userRepository, PaymentService paymentService) {this.userRepository = userRepository;this.paymentService = paymentService;}// 可选依赖@Autowired(required = false)public void setCacheManager(CacheManager cacheManager) {this.cacheManager = cacheManager;}
}
7.2 Lombok简化代码
@Service
@RequiredArgsConstructor // 为final字段生成构造函数
public class UserService {private final UserRepository userRepository;private final EmailService emailService;private final AuditService auditService;// Lombok自动生成:// public UserService(UserRepository userRepository, EmailService emailService, AuditService auditService) {// this.userRepository = userRepository;// this.emailService = emailService;// this.auditService = auditService;// }
}
总结
Spring推荐构造函数注入的主要原因:
优势 | 说明 |
---|---|
不可变性 | final字段确保依赖不会被意外修改 |
完整性保证 | 对象创建时所有依赖必须就绪 |
易于测试 | 不需要Spring容器即可创建实例 |
循环依赖检测 | 启动时就能发现设计问题 |
代码清晰 | 明确表达对象的依赖关系 |
线程安全 | 不可变对象天然线程安全 |
这种实践符合面向对象设计原则,特别是依赖倒置原则和单一职责原则,能够显著提高代码的质量和可维护性。