为什么在Java EE中使用CDI

52

我知道有很多文章解释如何在Java EE中使用CDI,但我现在很难理解这实际上带来了什么优势。例如,假设我有一个类目前使用Foo的一个实例。我可以选择

Foo myFoo = new Foo();
或者
// Better, FooFactory might return a mock object for testing    
Foo myFoo = FooFactory.getFoo();

我不停地读到,使用CDI可以做到:

@Inject
Foo myFoo;

但是,为什么这比以前基于工厂的方法更好?我想可能还有其他用例我不知道,但我还没有能够确定它们。

如果我正确理解下面的回答,概念是依赖注入框架作为一个集中配置的主对象工厂。这是一个合理的解释吗?

更新

此后,我开始学习Spring,现在这个概念更加清晰了。以下段落摘自 Spring实战的一个例子,讲述了一个AccountService类,该类又使用AccountDao的实例。对于这个很长的引文,我表示歉意,但我认为它真正说明了为什么注入资源比标准初始化要好。

您可以使用new关键字构造AccountService,但是服务层对象的创建很少是如此简单的。 它们通常依赖于DAO、邮件发送器、SOAP代理等等。 您可以通过AccountService构造函数(或静态初始化)程序化地实例化每个依赖项,但这会导致硬性依赖和级联变化,因为它们被替换掉了。

此外,您可以在外部创建依赖项,并通过setter方法或构造函数参数设置它们。这样做会消除硬内部依赖项(只要它们在AccountService中通过接口声明),但您将在各处重复初始化代码。以下是如何使用Spring创建DAO并将其与AccountService连接的方法:

<bean id="accountDao" class="com.springinpractice.ch01.dao.jdbc.JdbcAccountDao"/>

<bean id="accountService"
    class="com.springinpractice.ch01.service.AccountService">
    <property name="accountDao" ref="accountDao"/>
</bean>

按照上面的配置,您的程序现在可以从Spring ApplicationContext请求AccountService实例,Spring DI框架将负责实例化所有需要实例化的内容。


3
你在问“依赖注入有什么用处”? - Dave Newton
基本上是这样。我可以接受CDI比我的工厂类做得更多(如下所述),但我仍然不清楚那个更多是什么。 - PhilDin
1
我可能会搜索一些关于DI/IoC的通用信息。它有许多优点,duffymo对这条评论的回复很好地总结了它们。对我来说,前三个是关键,第四个只是一个结果,代理/面向切面编程并不太重要,尽管我都使用。 - Dave Newton
4个回答

50
CDI提供了一个大型的对象工厂,比你做得更好。它是基于XML配置或注释驱动的,因此您不必将所有内容嵌入代码中。
像Spring这样的依赖注入引擎所做的事情要比您的工厂多得多。复制它们提供的所有功能需要不止一个工厂类和一行代码。
当然,您不必使用它。您总是可以自己发明轮子。如果您的目的是学习如何制造轮子(译者注:原文链接失效)或消除依赖关系,那么您应该这样做。
但是,如果您只想开发应用程序,则最好使用其他人提供给您的工具,因为它们可以为您提供优势。
依赖注入的重要文章是由Martin Fowler撰写的。我建议阅读它;即使8年后它仍然很棒。
“仍然不清楚‘更多’是什么” 这里有一些优点:
  1. 更松散的耦合
  2. 更容易的测试
  3. 更好的分层
  4. 基于接口的设计
  5. 动态代理(引出AOP)。

它通过具有单一的配置点来提供这些功能,以确定将使用哪个具体实例来满足请求。我认为这很有道理。顺便感谢你提供的链接。 - PhilDin
2
不要忘记与Bean Validation、拦截器、轻量级事件和可移植扩展进行集成(几乎可以让您构建在Java EE中需要的任何东西)。 - LightGuard
1
我认为像Spring这样的现代框架也会关注另一件经常被忽略的事情,即正在使用的bean的生命周期。 - Bharat

19
使用依赖注入的目的是让使用被注入对象的代码不依赖于工厂。在你的工厂代码示例中,有一个静态方法调用嵌入在你的代码中,而采用 DI 的方法则不需要。被注入 myFoo 的对象不应该知道工厂的存在。使用工厂会限制你进行测试所需的选项,而 DI 则没有这些限制。特别地,如果你有一个包含 Foo 对象的 Bar 对象,那么对 Bar 对象的测试可以直接创建一个 Foo 或者模拟一个 Foo 并将其放在 Bar 上而不需要涉及到 FooFactory,并且你也不需要编写一个什么都不做的 FooFactory,因为它只是样板代码,没有实现任何应用程序业务逻辑。

6
这是一个关于企业编程的重要而微妙的问题。选用的名字非常恰当:上下文和依赖关系。
CDI与代码质量或清晰度无关,它主要是确保大型组织能够构建复杂分布式软件系统并共享数据。它的目的是为了确保政府或其他官僚机构可以随意分发自包含、良好记录的每一部分软件的包。请记住,现在几乎可以注入任何POJO。
假设你正在构建某种客户端应用程序,并且希望在角落打印用户的名字。
这个大公司的企业架构师希望你拥有这个功能,但是作为初级软件工程师,你不可能获得数据库的控制权;他们还希望在网络上保护数据,但是公司没有支付任何工程师来每次共享一点数据都重新设计认证客户端;他们希望你能够查询和更新此信息,但希望事务处理在任何一个应用程序之上实现;他们希望你能够在设置块中使用测试类进行简单的Mock测试;他们希望耦合度最小的类之间的耦合尽可能少涉及静态方法等等。
大多数JSR可能在某个地方隐藏着“企业架构师希望能够……”
CDI更受欢迎,因为它允许大型(任意?)横向和纵向规模的应用程序共享上下文、依赖项以及数据。
按照Fowler的话来说:
“问题是我该如何建立链接,使我的Lister类不知道实现类,但仍然可以与实例交流以完成其工作。”
“但是,如果我们希望以不同的方式部署此系统,则需要使用插件来处理与这些服务的交互,以便我们可以在不同的部署中使用不同的实现。”
“这些容器使用的方法是确保插件的任何用户都遵循一些约定,以允许单独的组装器模块将实现注入到列表器中。”
简而言之,它们允许集中管理复杂的企业应用程序。Java EE是一个系统化的、可靠的抽象过程,CDI是它的一种具体实现,它运行得非常好,几乎使其变得无形。它使得复杂应用程序的拼接几乎变得轻而易举。
还有两个事情:
  1. 请注意,CDI与“服务定位器模式”(在Java EE中称为JNDI)和平共处,如果客户端开发人员需要在许多类型相同的替代方案中进行选择,则此模式更可取。

  2. 在许多情况下,特别是非企业级(字面意思),CDI提供的功能过于强大。


4
在计算机科学中,与大多数事物一样,它提供了一种间接层(或抽象),否则将在应用程序中硬编码为Foo myFoo = new Foo();。该间接层带来了松耦合的代码,使得模块化变得容易,从而使其更容易替换、服务、测试等类或子系统的操作。
请注意,有许多设计和模式可用于间接/抽象 - 依赖注入只是其中之一。
你问题的另一个方面是“为什么使用CDI?” - 因为有人已经为你做了这项工作。你总是可以自己构建自己的东西,但当目标是构建一个必须在预算和时间内执行的实际系统时,这通常是浪费时间的。既然有米其林星级厨师愿意为你做这项工作,为什么还要费心去买菜做饭呢?

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