Java依赖注入:XML还是注解

35

注解越来越流行。Spring-3支持它们。CDI在很大程度上依赖它们(如果没有注解,我不能使用CDI,对吗?)

我的问题是为什么?

我听说了几个问题:

  1. “它有助于摆脱XML”。但是XML有什么不好的地方吗?依赖关系本质上是声明性的,而XML非常适合声明(但对于命令式编程来说非常糟糕)。 有了好的IDE(如Idea),编辑和验证XML非常容易,不是吗?

  2. “在许多情况下,每个接口只有一个实现”。那不是真的! 我的系统中几乎所有的接口都有测试的模拟实现。

还有其他问题吗?

现在是我对XML的优点:

  1. 你可以在任何地方注入任何东西(不仅仅是有注解的代码)

  2. 如果我有多个实现一个接口怎么办?使用限定符?但这会强制我的类知道它需要哪种注入。 这对设计来说不好。

基于XML的DI使我的代码清晰:每个类都不知道注入的内容,所以我可以以任何方式对其进行配置和单元测试。

你怎么看?

10个回答

24
我只能以使用Guice的经验为例说一下我的看法。简单来说,基于注解的配置极大地减少了连接应用程序所需编写的代码量,并使更改依赖关系变得更加容易...通常甚至不需要触及配置文件本身。它通过使最常见的情况非常简单来实现这一点,但以稍微较困难的方式处理某些相对罕见的情况为代价。
我认为过于教条地要求类“不了解注入”是一个问题。在类的代码中不应该有任何对注入容器的引用,我完全同意这一点。然而,我们必须明确一个观点:注解本身不是代码。它们本身并不会改变类的行为……即使有注解存在,您仍然可以像没有注解一样创建类的实例。因此,您可以完全停止使用DI容器并保留注解,没有任何问题。
当您选择不在类中提供关于注入的元数据提示(例如注解)时,您将放弃有关该类所需依赖项的有价值的信息源。您被迫在其他地方重复该信息(例如在XML中)或依靠不可靠的自动装配魔法,这可能导致意外问题。
针对您的一些具体问题:

它有助于摆脱XML配置

关于XML配置有很多问题。
  • 它非常冗长。
  • 没有特殊工具支持,它就不是类型安全的。
  • 它强制使用字符串标识符。同样,在没有特殊工具支持的情况下并不安全。
  • 它不利用语言的特性,需要使用各种丑陋的结构来完成可以用代码中的简单方法完成的事情。
尽管如此,我知道许多人已经使用XML已经很久了,他们认为它完全没问题,我也不指望改变他们的想法。

在许多情况下,每个接口只有一个实现

对于一个应用程序的单个配置(例如生产环境),通常每个接口只有一种实现方式。重点是,当启动您的应用程序时,通常只需要将一个接口绑定到一个实现即可,在许多其他组件中使用它。使用XML配置,您必须告诉使用该接口的每个组件使用该接口的这一个特定绑定(或“bean”如果您愿意)。使用基于注释的配置,您只需声明绑定一次,其他所有事情都会自动处理。这非常重要,并且大大减少了您需要编写的配置量。这也意味着,当您向组件添加新的依赖项时,通常根本不需要更改配置!

您拥有某些接口的模拟实现无关紧要。在单元测试中,您通常只需创建模拟对象并自行传递...与配置无关。如果您为集成测试设置了完整的系统,并使用某些接口而不是实际的实现...那也没有什么变化。对于系统的集成测试运行,您仍然只使用1种实现方式,并且只需要配置一次。

XML:您可以在任何地方注入任何内容

在Guice中,您可以轻松地做到这一点,我想您也可以在CDI中做到这一点。因此,使用基于注释的配置系统不一定会完全阻止您这样做。话虽如此,我敢说,在大多数应用程序中,大多数注入的类都是您自己可以添加@Inject的类(如果尚未存在)。轻量级标准Java库JSR-330的存在使得将来更多的库和框架为组件提供带有@Inject注释的构造函数变得更加容易。

一个接口的多个实现方式

一种解决方案是使用限定符,对于大多数情况这应该就足够了。但在某些情况下,您可能想要做一些特殊操作,而仅在特定注入类的参数上使用限定符并不起作用...很可能是因为您想要有多个类的实例,每个实例都使用不同的接口实现或实例。Guice使用称为PrivateModule的东西来解决这个问题。我不知道CDI在这方面提供了什么。但再次强调,这只是少数情况,只要您能处理,就没有必要让其它配置受影响。


1
我唯一能想到的关于注释的缺点是,当您有一个包含某些可能使用 DI 的类的库时,注释将会将 Spring 依赖项拖入该库中。如果在使用库的应用程序中使用 xml,则只需应用程序需要依赖于 Spring。(假设我们谈的是 Spring DI,我相信 Guice 在这方面也没有什么不同)。 - Ivaylo Slavov

10
我有以下原则:配置相关的bean使用XML定义,其他所有的bean使用注释。
为什么?因为你不想在类中更改配置。另一方面,在要启用的类中编写 @Service 和 @Inject 会更简单。
这并不会以任何方式影响测试 - 注释只是由容器解析的元数据。如果您喜欢,可以设置不同的依赖项。
至于CDI - 它有一个用于XML配置的扩展,但您是对的,它主要使用注释。虽然我不是特别喜欢这一点。

4

我喜欢保持我的代码清晰,就像你指出的那样。XML对于我来说在IOC原则方面更好。

依赖注入(Dependency Injection)的基本原则是:应用程序对象不应负责查找它们所依赖的资源或协作者。相反,IoC容器应配置对象,将资源查找从应用程序代码外部化到容器中。(《J2EE Development without EJB》- Rod Johnson - 131页)

再次强调,这只是我的观点,没有什么原教旨主义 :)

编辑: 一些有用的讨论如下:


1
注解是元数据,它们不参与查找。唯一的区别在于元数据的位置。并且它不违反IoC原则。 - Bozho
2
我同意。我并不是想说注入违反了IoC原则。我的意思是,将元数据从类中分离出来对我来说更加清晰明了。 - Aito
个人认为,承认使用 DI 容器是可以的,因为这样做可以获得真正的好处而没有任何不利影响。 - ColinD

4
在我看来,这更多是一个品味问题。
1)在我们的项目中(使用Spring 3),我们希望XML配置文件只是配置。如果它不需要从最终用户的角度进行配置,或者其他问题不强制在xml中完成,则不要将bean定义/连接放入XML配置中,而是使用@Autowired等注释。
2)使用Spring,您可以使用@Qualifier匹配接口的特定实现(如果存在多个)。是的,这意味着您必须命名实际实现,但我不介意。
在我们的情况下,使用XML处理所有DI会使XML配置文件膨胀很多,尽管可以在单独的xml文件(或文件)中完成,因此这不是一个非常有效的观点;)。正如我所说,这是一个品味问题,我认为通过注释处理注入更容易和更干净(您可以通过查看类而不是浏览XML文件查找bean声明来查看某些服务/存储库/任何内容使用什么)。
编辑:这里有一篇关于@Autowired与XML的观点,我完全同意:Spring @Autowired usage

3
“但 XML 有什么不好的呢?” 这只是又一个需要管理的文件和寻找错误的地方。如果您的注释紧贴在代码旁边,那么管理和调试就会变得更加容易。

2

像所有事情一样,依赖注入应该适度使用。此外,所有依赖注入相关的东西都应该与应用程序代码分开,并放置在与主要代码相关的代码中。

通常,应用程序应该有一个边界,将抽象的应用程序代码与具体的实现细节分开。所有跨越这个边界的源代码依赖关系都应指向应用程序。我称这个边界的具体一侧为主要分区,因为“main”(或其等效物)应该位于那里。

主要分区由工厂实现、策略实现等组成。在这一侧上,依赖注入框架应该进行工作。然后可以通过正常手段(例如作为参数)将这些注入的依赖项传递到应用程序中。

注入的依赖项数量应该相对较小。十二个或更少。在这种情况下,XML或注释之间的选择是无意义的。


1

在我看来,这是一种可行的注释替代方案。但它不能替代XML配置。 - Stijn Van Bael

1
在我的情况下,编写应用程序的开发人员与配置它的开发人员不同(不同的部门、不同的技术/语言),而且最后一组甚至没有访问源代码的权限(这在许多企业设置中都是这样的)。这使得Guice无法使用,因为我必须公开源代码,而不是使用实现应用程序的开发人员配置的xml。
总的来说,我认为重要的是要认识到提供组件和组装/配置应用程序是两个不同的练习,并在需要时提供这种关注点的分离。

2
看起来 Guice 可以直接读取配置文件(属性):https://dev59.com/ZG445IYBdhLWcg3wiq8i#4805982 - mjn

0

XML具有声明式样式的唯一优点,它明确定义与应用程序代码分离。这保持独立于DI方面的关注。缺点是冗长、重构鲁棒性差以及一般运行时失败行为。与例如Java的IDE支持相比,只有一般(XML)工具支持很少有益处。此外,XML带来了性能开销,所以通常比代码解决方案慢。

注释常常更直观和强大,在重构应用程序代码时更加稳健。此外,它们受益于像guice提供的更好的IDE指导。但是它们将应用程序代码与DI问题混合在一起。一个应用程序会依赖于一个框架。几乎不可能进行明确的分离。注释在描述同一位置(构造函数、字段)上的不同注入行为时也受到限制,这取决于其他情况(例如机器人腿问题)。此外,它们不允许像自己的源代码一样处理外部类(库代码)。因此,它们被认为比XML运行得更快。

这两种技术都有严重的缺点。因此,我建议使用Silk DI。它是在代码中声明定义的(具有出色的IDE支持),但与您的应用程序代码完全分离(无框架依赖)。它允许将所有代码视为相同,无论是来自您的源代码还是外部库。像机器人腿问题这样的问题可以通过常规绑定轻松解决。此外,它具有良好的支持,以使其适应您的需求。


0

我只想补充一些已经存在的内容。

  • 对我来说,DI配置就是代码。我希望将其视为代码,但XML的本质使得这需要额外的工具才能实现。

  • Spring JavaConfig在这方面是一个重大进步,但仍然有复杂性。组件扫描、自动选择接口实现以及关于@Configuration注释类的CGLIB拦截的语义使其比必要的更加复杂。但它仍然比XML更先进。

  • 将IoC元数据与应用程序对象分离的好处被夸大了,特别是对于Spring而言。也许如果你仅限于Spring IoC容器,这是正确的。但Spring提供了广泛的应用程序堆栈,构建在IoC容器上(安全性、Web MVC等)。一旦你利用了其中任何一个,你就会被绑定到容器上。


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