为什么在CDI中使用构造函数而不是setter注入?

时间:2013-10-15 12:56:54

标签: java dependency-injection cdi

我在SO上找不到任何合理的答案所以我希望它不是重复的。那么为什么我更喜欢setter或构造函数注入而不是简单的

@Inject
MyBean bean;

如果你需要在类初始化期间对注入的bean执行某些操作,我会使用构造函数注入,如

public void MyBean(@Inject OtherBean bean) {
    doSomeInit(bean);
    //I don't need to use @PostConstruct now
}

但是,它几乎和@PostConstruct方法一样,我根本没有得到setter注入,它不仅仅是Spring和其他DI框架之后的遗留物吗?

3 个答案:

答案 0 :(得分:16)

构造函数和属性注入使您可以轻松地在非CDI环境中初始化对象,例如单元测试。

在非CDI环境中,您仍然可以通过传递构造函数arg来简单地使用该对象。

OtherBean b = ....;
new MyBean(b);

如果你只是使用场注入,你通常必须使用反射来访问该字段,因为字段通常是私有的。

如果使用属性注入,您还可以在setter中编写代码。例如。验证代码或清除内部缓存,其中包含从setter修改的属性派生的值。您想要做什么取决于您的实施需求。

Setter vs构造函数注入

在面向对象的编程中,对象必须在构造之后处于有效状态,并且每个方法调用都会将状态更改为另一个有效状态。

对于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;
     // We can be sure that the dataSource is not null
     Connection con = dataSource.getConnection();
     ...
     return customer;
  }
}

我的结论

  • 对每个可选依赖项使用属性
  • 对每个强制性依赖使用构造函数args

PS:我的博客The difference between pojos and java beans更详细地解释了我的结论。

答案 1 :(得分:4)

使用CDI 时,没有理由使用构造函数或setter注入。如问题所述,您可以为构造函数中的其他方法添加@PostConstruct方法。

其他人可能会说你需要使用Reflection在单元测试中注入字段,但事实并非如此;模拟库和其他测试工具为您做到这一点。

最后,构造函数注入允许字段为final,但这并不是@Inject - 注释字段(不能是final)的缺点。注释的存在,加上没有明确设置字段的任何代码,应该清楚地表明它只能由容器(或测试工具)设置。在实践中,没有人会重新分配注入的场地。

过去,构造函数和setter注入是有意义的,因为开发人员通常必须手动实例化并将依赖项注入到测试对象中。如今,技术已经发展,现场注入是一个更好的选择。

答案 2 :(得分:1)

可接受的答案很好,但是并不能归功于构造函数注入的主要优点-类不变性,这有助于实现线程安全性,状态安全性以及对类的更好可读性

考虑到您的类具有依赖项,并且所有这些依赖项都作为构造函数参数提供,那么您可以知道该对象永远不会在依赖项无效的状态下存在。不需要这些依赖项的setter(只要它们是私有的),因此对象被实例化为完全状态或完全不被实例化。

不可变对象在多线程应用程序中的运行可能性更大。尽管仍然需要使该类在内部具有线程安全性,但是您不必担心外部客户端会协调对对象的访问。

当然,这仅在某些情况下有用。 Setter注入非常适合局部依赖,例如,我们在一个类中具有3个属性,并具有3个arg构造函数和setters方法。在这种情况下,如果只想传递一个属性的信息,则只能通过setter方法传递。对于测试目的非常有用。