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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19381846/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 16:37:59  来源:igfitidea点击:

Why use constructor over setter injection in CDI?

javadependency-injectioncdi

提问by Petr Mensik

I couldn't find any reasonable answer here on SO so I hope it's not a duplicate. So why should I prefer setter or constructor injection over simple

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

@Inject
MyBean bean;

I get the usage of the constructor injection if you need to do something with injected bean during your class initialization like

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

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

but still, it's almost the same like @PostConstructmethod and I don't get setter injection at all, isn't it just a relic after Spring and other DI frameworks?

但是仍然,它几乎相同的@PostConstruct方法,我根本没有得到setter注入,这不就是Spring和其他DI框架之后的遗物吗?

采纳答案by René Link

Constructor and property injection gives you the option to initialize the object even in a non CDI environment easily, e.g a unit test.

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

In a non-CDI environment you can still simply use the object by just passing the constructor arg.

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

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

If you just use field injection you usually must use reflection to access the field, because fields are usually private.

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

If you use property injection you can also write code in the setter. E.g. validation code or you clear internal caches that hold values which are derived from the property that the setter modifies. What you want to do depends on your implementation needs.

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

Setter vs constructor injection

Setter 与构造函数注入

In object-oriented programming an object must be in a valid state after construction and every method invocation changes the state to another valid state.

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

For setter injection this means that you might require a more complex state handling, because an object should be in a valid state after construction, even if the setter has not been invoked yet. Thus the object must be in a valid state even if the property is not set. E.g. by using a default value or a null object.

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

If you have a dependency between the object's existence and the property, the property should either be a constructor argument. This will also make the code more clean, because if you use a constructor parameter you document that the dependency is necessary.

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

So instead of writing a class like this

所以不要写这样的类

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

}

you should either use constructor injection

你应该使用构造函数注入

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

My conclusion

我的结论

  • Use propertiesfor every optional dependency.
  • Use constructor argsfor every mandatory dependency.
  • 为每个可选依赖项使用属性
  • 对每个强制依赖使用构造函数参数

PS: My blog The difference between pojos and java beansexplains my conclusion in more detail.

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

回答by Rogério

When using CDI, there is no reason whatsoever to use constructor or setter injection. As noted in the question, you add a @PostConstructmethod for what would otherwise be done in a constructor.

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

Others may say that you need to use Reflection to inject fields in unit tests, but that is not the case; mocking libraries and other testing tools do that for you.

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

Finally, constructor injection allows fields to be final, but this isn't really a disadvantage of @Inject-annotated fields (which can't be final). The presence of the annotation, combined with the absence of any code explicitly setting the field, should make it clear it is to be set by the container (or testing tool) only. In practice, no one will be re-assigning an injected field.

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

Constructor and setter injection made sense in the past, when developers usually had to manually instantiate and inject dependencies into a tested object. Nowadays, technology has evolved and field injection is a much better option.

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

回答by Piotr Niewinski

Accepted answer is great, however it doesn't give credit to the main advantage of constructor injection- class immutability, which helps to achieve thread-safety, state safety, and better readability on the classes.

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

Consider you have class with dependencies and all of those dependencies are provided as constructor arguments, then you can know that the object will never exist in a state where dependencies are invalid. There is no need for setters for those dependencies (as long as they are private), so the object is instantiated to a full state or is not instantiated at all.

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

An immutable object is much more likely to well behave in an multithreaded application. Although the class still needs to be made internally thread-safe, you don't have to worry about external clients coordinating access to the object.

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

Of course this can be usefull only in certain scenarios. Setter injectionis great for partial depencdency, where for example we have 3 properties in a class and 3 arg constructor and setters methods. In such case, if you want to pass information for only one property, it is possible by setter method only. Very useful for testing purposes.

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