为什么Spring不支持直接的字段依赖注入(除了@Autowired)?

20

我对直接字段依赖注入很感兴趣。传统上,Spring支持构造函数注入(向构造函数提供参数)和基于setter的注入(在调用时调用setter)。

然而,Spring也能够进行直接字段注入(设置对象的成员字段而不使用setter方法),这可以通过使用@Autowired注释字段来证明。自动装配仅限于“bean”,因此不能注入原始值(尽管可以通过创建类为“java.lang.String”的bean来绕过这个问题 - 这可以工作,但具有自动装配的普通警告)。除此之外,Spring还支持@Value从属性等直接将值设置到成员字段中。

然而,Spring不允许属性直接设置为成员字段(而不进行自动装配)。

我的问题是:为什么?

显然它有能力这样做,那为什么没有呢?是否存在任何严重的负面影响阻止了这样做?或者说这种能力受到限制,因此只有自动装配才有意义?需要比调用setter更大的hack吗?

请注意,我不希望讨论一般情况下是否应该拥有setter和getter,只是想了解Spring做出这个选择的原因。


1
我一直想知道同样的事情,并且认为原因与“编写干净的代码”有关,以及防止编译器发出警告,指出该值从未被设置,而您正在尝试使用它。 - Andrew White
3个回答

10

@Autowired注解使用反射使私有字段可被访问 (详见这个相关问题)。我可以看到三个原因,为什么它不在Spring配置文件中使用:

  1. 由于配置是通过bean属性(getter和setter)进行的,在可能存在两者的情况下,你无法真正判断是否想要调用setValue还是设置成员变量的值。
  2. 它会破坏封装性。您的Spring配置没有理由了解私有成员变量。使用注释是可以的,因为它已经在源代码中。
  3. 安全问题。更严格的安全管理器可能不允许通过反射访问私有字段。

私有的setter注入也不是一个好主意。我强调第二点:如果我访问私有成员或方法,无论如何它仍然会破坏封装并违反面向对象编程和依赖注入的原则。还将存在相同的安全问题。 - Daff
3
如果设置方法是public,那么成员字段为什么不应该是public呢?或者,如果setter是private,那么如何设置一个private成员字段会有更多的安全隐患呢? - Nakedible
封装(面向对象编程):隐藏对象的内部保护其完整性,防止用户将组件的内部数据设置为无效或不一致状态。这就是为什么字段不应该是公共的原因。现在再次强调,我认为私有setter方法是一种不好的实践,只有在极为罕见的情况下才会使用它们。 - Daff
1
这是JavaBean规范的一部分:http://en.wikipedia.org/wiki/JavaBean “类属性必须使用get、set、is可访问。”因此,如果你将成员公开而不提供存取器,则不遵循规范,因此没有JavaBean。如果您想添加其他检查但已在代码中直接访问字段,会发生什么? - Daff
我发现遵循标准和面向对象编程的最佳实践比调整你的类以适应使用的依赖注入框架更容易。我理解你的观点,我认为在Java中使用getter和setter的方式实现是一种语言限制。 - Daff
显示剩余5条评论

10

我认为我已经找到了答案。我查看了Spring源代码,看到了这些特性的实际实现方式。以下是我的发现:

通过XML设置属性可能是Spring中最早的部分,它非常依赖于“java.beans”类来进行内省、属性枚举等操作。显然,这些类根本不支持字段内省。在此基础上还有类型转换机制,用于确定如何将属性值转换为适合相应属性的值。这里没有明确可分离的部分。

所有的@Autowired等内容都是在BeanPostProcessor中实现的,该处理器具有自己的类型匹配机制,与类型转换无关。这也是为什么它只注入bean。@Value也基本上是一样的,它只是在那里解决了问题,并且与属性没有任何关系。

因此,为属性添加字段注入支持并不是一个简单的工程努力,因为执行其中之一的代码部分与另一个几乎完全分开。

这并不能完全回答“为什么”,但我认为这比我听到的其他解释更令人信服,说明了为什么Spring没有添加直接的字段依赖注入。除非他们对此有根本性的反对意见(我怀疑,因为他们想允许对现有第三方类进行配置,而不仅仅是JavaBeans),否则,这只是一项工程努力的问题,以获得该功能。


1

它通过JSR-250 @Resource注释(在Spring 3.0+中)支持此功能。

个人而言,我更喜欢这种方式而不是setter注入,并且在考虑构造函数注入时对此有复杂的感觉。以下是我考虑过的问题:

1)构造函数注入是自我记录bean依赖项的好方法(优点),但会创建很多DRY违规:(a)私有字段,(b)构造函数参数,(c)构造函数代码以从参数设置字段,(d)bean配置中的附加代码(无论是在@Configuration类中还是在xml中)。为了一些封装纯度而产生了大量DRY违规,我甚至不关心。这几乎不是封装的违规 - 但它确实在某种程度上依赖于某些遵守JSR-250注释的注入容器(见下文)。

2) 创建了对JSR-250兼容容器的依赖。我对此有复杂的感受。当我第一次听说@Resource时,我认为它会使我的系统更难测试,于是我将其抛之脑后。然而,最终我还是在我的测试中使用了Spring。我仍然可以像往常一样使用模拟bean,所以这从来不是一个问题。问题在于,在测试之外,你真正想要重用它的时候是什么时候呢?在我看来,如果你正在设计系统以利用容器,则应该拥抱它并使用它。追求灵活性是否值得违反DRY原则,不在容器内运行?至少使用JSR-250注释,您可以在任何JEE6环境中运行并获得所需的注入。

3) 如果在容器之外实例化一个对象,则可能会创建一些不太好的调试场景:例如,您将获得空指针异常而不是友好的提示。这是一种权衡。我个人认为这并不可怕——我的意思是,如果您在带有@Resource的行上获得NPE,那么您很快就会意识到它没有被注入。


@Resource注解是按名称自动装配的,这不是我想要的。 - Nakedible

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