Spring中的Setter DI与Constructor DI有什么区别?

35

Spring有两种DI(Dependency Injection)类型:构造函数DI和Setter DI。

基于构造函数的DI固定了注入依赖项的顺序。而基于Setter的DI则没有这样的要求。

Setter DI帮助我们在需要时才注入依赖项,而不是在构建对象时就需要注入它们。

我并没有看到其他显著的区别,因为两种类型的Spring DI提供了相同的功能- 无论是setter DI还是constructor DI都会在代码启动时注入依赖项。尽管构造函数DI通过构造函数完成,而Setter DI将在构建对象后通过一个Setter进行注入,但从开发人员的角度来看,在性能等方面并没有区别。 它们都提供了指定注入依赖项顺序的手段。

我寻找一种场景,在这种场景下,一种方法比另一种方法具有明显优势,或者其中一种类型完全无法使用。


https://dev59.com/PFkS5IYBdhLWcg3wamJy - Grigory Kislin
9个回答

38

谈到Spring的专有优缺点:

  • (从定义上)构造函数注入不允许您在bean之间创建循环依赖关系。这个限制实际上是构造函数注入的优点 - 当使用setter注入时,Spring可以在您甚至没有注意到的情况下解决循环依赖。

  • 另一方面,如果使用构造函数注入,则CGLIB无法创建代理,强制您使用基于接口的代理或虚拟的空参构造函数。请参见:SPR-3150


感谢Tomasz。第一点:在循环依赖的情况下,Spring会产生运行时错误吗?第二点:只是试图通过示例来理解。你是说如果我有一个类A,在它的构造函数中使用类B作为依赖关系(类B没有实现接口),这是不可能的吗? - M Sach
6
想一想:如果bean A在启动时需要bean B的实例,而bean B又需要一个bean A的实例,那么它们就无法被实例化。当Spring检测到这种情况时,在启动时会抛出异常。但是使用setter/field注入,Spring可以先创建实例,然后再注入它们,在这种情况下不会有问题。 - Tomasz Nurkiewicz
3
如果在你的项目中这是一个常见问题,那么你可能有其他需要担心的事情,而不仅仅是依赖注入风格。 - Ryan Stewart

25

你应该基于设计考虑而不是工具(Spring)考虑来做决策。不幸的是,Spring 让我们使用 setter 注入,因为当它最初被设计时,Java 中没有"注解"这个概念,在 XML 中,setter 注入的效果也更好。今天,我们被释放了这些限制,从而使它再次成为一种设计决策。你的 bean 应该对任何必需的依赖项使用构造函数注入,并且对于可选依赖项并且有一个合理的默认值,则使用 setter 注入,或者说这就像面向对象设计一直在告诉我们的那样。


1
谢谢Ryan。正如您所说,“您的Bean应使用构造函数注入来获取Bean所需的任何依赖项,并使用Setter注入来获取可选依赖项和具有合理默认值的依赖项”。这就是我试图在Spring方面理解的,即使依赖关系是可选的,框架仍将在开头注入它。那么这对开发人员的生活有什么不同呢? - M Sach
“当”某些东西被注入和“如果”某些东西被注入是两个不同的问题。可选依赖项属于“如果”的范畴。 - Ryan Stewart

17

构造函数注入:我们通过构造函数来注入依赖项。

通常用于强制性依赖项。

如果使用构造函数注入,则存在一个缺点,称为“循环依赖”。

循环依赖:假设有A和B。 A依赖于B。 B依赖于A。在这种情况下,构造函数注入将失败。此时,使用Setter injection是有用的。

如果对象状态不一致,则不会创建对象。

Setter注入:我们通过Setter方法注入依赖项。

这对于非强制性依赖项非常有用。

使用Setter Injection 可以进行重新注入 依赖项。而在构造函数注入 中则不可能。


3
为什么你称防止循环依赖为缺点?我认为这是优点,因为它可以防止程序员出现紧密耦合的情况。 - the hand of NOD

8

根据spring.io上的内容,从Spring 5开始

由于可以混合使用基于构造函数和基于setter方法的DI,因此一个好的经验法则是对于必需的依赖关系使用构造函数,对于可选的依赖关系使用setter方法或配置方法。请注意,在setter方法上使用@Required注释可以将属性设置为必需依赖项。

Spring团队通常提倡使用构造函数注入,因为它使得应用程序组件能够作为不可变对象实现,并确保所需的依赖项不为空。此外,通过构造函数注入的组件始终以完全初始化的状态返回给客户端(调用)代码。顺便说一句,大量的构造函数参数是糟糕的代码味道,意味着该类可能具有过多的职责,应进行重构以更好地处理关注点分离。

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

这里是上述引用的链接

但是,所有注入类型都可用,没有任何一个被弃用。在高层次上,您可以在所有注入类型中获得相同的功能。

简而言之,选择最适合您团队和项目的注入类型。

Spring团队和独立博客文章的建议会随时间而变化。没有硬性规定。

如果Spring团队不推荐特定的注入方式,则他们会将其标记为已弃用或过时。但对于任何一种注入方式都不是这种情况。


1

我个人的看法:

假设有一个类A,它有10个字段,其中有一些是注入的依赖项

如果您需要整个类A的所有字段,那么可以选择使用构造函数注入

但是,如果您只需要在该类中使用一个注入的字段,则可以使用setter注入

这样,

  1. 您不需要每次都创建新对象。
  2. 您不需要担心循环依赖问题(BeanCurrentlyInCreationException)。
  3. 您不需要为类A创建其他字段,因此代码更加灵活。

0

由于您可以混合使用构造函数依赖注入和基于Setter的依赖注入,因此一个好的经验法则是使用构造函数参数来传递必需的依赖项,使用Setter来传递可选的依赖项


请注意,在Setter上使用@Required注释可以用于使Setter成为必需依赖项。


0

可能这不是它的主要优势,但让我解释一下Spring中注入机制。

这两种方法的区别在于,使用@Inject、@Autowire等方式进行注入时,Spring将使用反射将一个bean注入到另一个bean中,而使用构造函数的方式,则是我们自己使用构造函数来初始化一个bean,而不使用反射。
因此,使用构造函数的方式比其他选项更好,至少我们不使用反射机制,因为从机器方面来看,反射是一种昂贵的操作。
P.S. 请注意,正确使用构造函数DI是当您通过带参数的构造函数手动创建bean时,即使您可以使用不带任何参数的构造函数创建bean。

0

更喜欢使用setter注入。

想象一下如果没有Spring会怎样(正如Ryan所说)。你会在构造函数中传递依赖项吗?如果有太多的依赖项,这似乎是错误的。另一方面,构造函数可以用于强制对象的有效状态 - 要求所有依赖项并验证它们是否为非空。

代理是另一回事(正如Tomasz所指出的) - 你需要一个虚构的构造函数来破坏整个想法。

顺便说一下,还有第三种选择 - 字段注入。我倾向于使用它,尽管这不是一个很好的设计决策,因为它节省了额外的setter,但如果这在Spring之外使用,我将不得不添加setter。


1
事实上,使用构造函数注入的副作用是由于巨大的构造函数使您的设计不良的bean变得非常明显。JDK动态代理不需要默认构造函数。其他框架正在远离默认构造函数或仅要求私有构造函数。字段注入是我的第二选择,但是这时就需要进行测试... - Ryan Stewart
是的,对于测试来说有ReflectionTestUtils。虽然有点丑陋。 - Bozho

-2
不,即使使用构造函数注入,注入仍然有效,但只是有限的初始化,setter注入是可选和灵活的。但通常它适用于参数类,一个Spring bean与其他Spring beans一起使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接