33

我在这里找不到任何合理的答案,所以我希望它不是重复的。那么为什么我应该更喜欢 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 框架之后的遗物吗?

4

3 回答 3

40

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

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

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

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

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

Setter 与构造函数注入

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

对于 setter 注入,这意味着您可能需要更复杂的状态处理,因为对象在构造后应该处于有效状态,即使尚未调用 setter。因此,即使未设置属性,对象也必须处于有效状态。例如,通过使用默认值或空对象

如果对象的存在和属性之间存在依赖关系,则属性应该是构造函数参数。这也将使代码更干净,因为如果您使用构造函数参数,则说明依赖项是必要的。

所以不要像这样写一个类

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;
  }
}

我的结论

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

PS:我的博客pojos和java beans的区别更详细的解释了我的结论。

编辑

Spring 还建议使用构造函数注入,正如我在 spring 文档的Setter-based Dependency Injection部分中找到的那样。

Spring 团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不为空。此外,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。附带说明一下,大量的构造函数参数是一种不好的代码气味,这意味着该类可能有太多的职责,应该重构以更好地解决适当的关注点分离问题。

Setter 注入应该主要只用于可以在类中分配合理默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。setter 注入的一个好处是 setter 方法使该类的对象可以在以后重新配置或重新注入。因此,通过 JMX MBean 进行管理是 setter 注入的一个引人注目的用例。

当您考虑单元测试时,构造函数注入也是一种更好的方法,因为调用构造函数而不是设置私有(@Autowired)字段更容易。

于 2013-10-15T13:08:06.980 回答
3

使用 CDI时,没有任何理由使用构造函数或 setter 注入。如问题中所述,您添加了一个@PostConstruct方法,否则将在构造函数中完成。

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

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

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

于 2015-02-24T21:01:25.070 回答
2

公认的答案很好,但是它并没有将构造函数注入的主要优势归功于类的不变性,这有助于实现线程安全、状态安全和更好的类可读性。

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

不可变对象更有可能在多线程应用程序中表现良好。尽管类仍然需要在内部实现线程安全,但您不必担心外部客户端协调对对象的访问。

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

于 2020-01-29T20:33:46.767 回答