构造函数和属性注入为您提供了在非 CDI 环境中轻松初始化对象的选项,例如单元测试。
在非 CDI 环境中,您仍然可以通过传递构造函数参数来简单地使用对象。
OtherBean b = ....;
new MyBean(b);
如果你只使用字段注入,通常必须使用反射访问字段,因为字段通常是私有的。
如果你使用属性注入,你还可以在setter中编写代码。例如验证代码或者清除内部缓存,这些缓存保存了从setter修改的属性派生出来的值。你想要做什么取决于你的实现需求。
Setter vs constructor注入
在面向对象编程中,一个对象必须在构造后处于有效状态,每个方法调用都会将状态更改为另一个有效状态。
对于setter注入,这意味着您可能需要更复杂的状态处理,因为即使setter尚未被调用,对象也应该在构造后处于有效状态。因此,即使未设置属性,对象也必须处于有效状态。例如通过使用默认值或null object。
如果对象的存在和属性之间存在依赖关系,则属性应该是构造函数参数。这样也会使代码更加干净,因为如果您使用构造函数参数,您就可以记录依赖关系是必需的。
所以,不要编写这样的类:
public class CustomerDaoImpl implements CustomerDao {
private DataSource dataSource;
public Customer findById(String id){
checkDataSource();
Connection con = dataSource.getConnection();
...
return customer;
}
private void checkDataSource(){
if(this.dataSource == null){
throw new IllegalStateException("dataSource is not set");
}
}
public void setDataSource(DataSource dataSource){
this.dataSource = dataSource;
}
}
您应该使用构造函数注入。
public class CustomerDaoImpl implements CustomerDao {
private DataSource dataSource;
public CustomerDaoImpl(DataSource dataSource){
if(dataSource == null){
throw new IllegalArgumentException("Parameter dataSource must not be null");
}
this.dataSource = dataSource;
}
public Customer findById(String id) {
Customer customer = null;
Connection con = dataSource.getConnection();
...
return customer;
}
}
我的结论:
- 对于每个可选依赖项,请使用属性(properties)。
- 对于每个强制依赖项,请使用构造函数参数(constructor args)。
PS:我的博客POJO与Java Bean的区别更详细地解释了我的结论。
编辑:
Spring文档中也建议使用构造函数注入,详见章节Setter-based Dependency Injection。
Spring团队一般推荐使用构造函数注入,因为它可以让您将应用程序组件实现为不可变对象,并确保所需的依赖项不为null。此外,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。顺便提一下,大量的构造函数参数是糟糕的代码气味,暗示着该类可能具有过多的职责,并且应进行重构以更好地解决关注点分离问题。
应主要用于可在类内分配合理默认值的可选依赖项的Setter注入。否则,代码中使用依赖项的每个地方都必须执行非空检查。 Setter注入的一个好处是,setter方法使该类的对象适合于稍后重新配置或重新注入。因此,通过JMX MBeans进行管理是Setter注入的一个引人注目的用例。
考虑到单元测试,构造函数注入也是更好的方式,因为调用构造函数比设置私有(@Autowired)字段更容易。