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


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


11878
2017-10-15 12:56


起源

可能重复 通过构造函数或属性设置器进行依赖注入? - artbristol
我不这么认为(我已经读过这篇文章了),因为他们正在讨论是否更好地使用构造函数或setter,我在这里问如果我可以使用字段注入,那么setter或构造函数注入的目的是什么,那么为什么要downvote呢? - Petr Mensik


答案:


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

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

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

如果您只是使用场注入,则必须使用反射来访问该字段(例如,如果它是私有的)。

如果使用属性注入,也可以在setter中编写代码。所以这取决于您的实施需求。

塞特与构造函数注入

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

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

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

所以不要写这样的课

public class CustomerDaoImpl implements CustomerDao {

  private DataSource dataSource;

  public Customer findById(String id){
     // Is the dataSource set?!
     Connection con = dataSource.getConnection();
     ...
     return customer;
  }

  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:我的博客 pojos和java bean之间的区别 更详细地解释了我的结论。


12
2017-10-15 13:08



好点,谢谢! (虽然现在不需要使用Arquillian框架) - Petr Mensik
这是我能想到的唯一原因。 - LightGuard
构造函数注入还允许将类字段声明为 final。使用属性或setter注入时,这是不可能的。 - Pavel Horal
使用构造函数注入,您可以使bean不可变。 - Yuri
我有一个案例,我们正在为一些外部项目准备API。我们有一个更复杂的类层次结构,有许多抽象的超类。添加基于构造函数的注入将强制在每个继承级别上创建构造函数。这会产生大量不安全/容易破坏的代码。如果外部项目的开发人员想扩展我们的类,他们也必须创建这样的构造函数。这将导致完全混乱和不可靠的代码。 - AndrewMcCoist


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

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

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

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


4
2018-02-24 21:01



IDE为您生成它,没有任何缺点。即使使用“演进技术”工具,现场注入模拟也变得很困难,因为除非您检查代码,否则您不知道需要为初始化提供哪些依赖项。这些第三方工具也会带来性能影响,这是单元测试的祸根。谁想要使用Weld来执行@PostConstruct并依赖其他第三方组件,只需要构造函数? - highstakes
@highstakes用于注入字段的工具与人们已经用于模拟的工具相同,因此不需要像Weld这样的东西。并且对于所有实际目的而言,性能影响是不存在的。不,现场注射 是到目前为止,最好的选择;构造函数只会在代码中添加噪声,无论是否由IDE生成;他们没有帮助“知道你需要提供什么依赖关系” 注释 田野呢。此外,CDI / Java EE(最适合Java的DI)主要面向现场注入。 - Rogério
我知道它可能不太方便,但构造函数是您与外部通信的API的一部分。如果您开始使用隐藏的私有字段依赖项,则会产生不必要的混淆。通过同样的努力,我可以使用PowerMock来模拟静态依赖关系,那么为什么还要为控制反转而烦恼呢? - highstakes
@highstakes构造函数不是API的一部分,实际上,它只会由DI容器或测试库调用。要注入的字段不是隐藏的,因为它们是 注释 使用标准DI注释(@Inject, @Resource)或其他众所周知的注释,如 @Autowired。没有任何混淆的可能性。和 “控制倒置” 与依赖注入(DI)无关。 - Rogério
使用构造函数注入,如果向构造函数添加新的依赖项,则不会编译测试。对我来说这是一件好事。使用字段或setter,注入,您的测试仍将编译,并且可能会失败,具体取决于它们如何使用新添加的依赖项。 - Magnilex