快捷搜索:

Apache Geronimo 中的依赖注入,第 1 部分: 用新的方

软件开拓职员不停在追求代码重用——缘故原由很显着。这种追求的措施跟着光阴而变更,从 Fortran 中的函数到面向工具编程(OOP)和面向接口的承袭。在每一步上,我们都邑发明比曩昔更好的技巧可以让我们把代码从硬依附中解耦出来。此中前进代码重用最好的一个要领便是把接口与实现解耦。在 Art of UNIX Programming 一书中,Eric Raymond 把这一点融入了几个 UNIX® 原则:

模块化原则:编写由整齐的接口连接的简单部件。

分离原则:把策略与机制分离;把接口与引擎分离。

表示原则:把常识放在数据中,这样法度榜样逻辑就可以又蠢又壮。

虽然这些不雅点老了,但我们仍旧赓续地探求用 Java™ 技巧实现它们的新要领。最新一轮解耦 —— 依附注入,就反应了上面描述的不雅点。像在不合地方以不合要领实现的许多新观点一样,在观点和实现之间呈现了许多纷乱。在这个由两部分构成的系列中,我评论争论了 DI 的观点(也叫做节制反转 或 IoC),然后演示它在 Apache Geronimo 中的实现要领。

DI 与 IoC

DI 框架造成纷乱的一个方面是用来描述它们的术语。您据说过节制反转 和依附注入 险些可以交换应用。然则,它们指的并不是一回事。

IoC 是多半构架的一个通用术语。字面上说,它意味着把节制元素转变为一样平常来说被它节制的元素上(受控者)。换句话说,框架中平日的节制部件变成了被节制的部件。例如,在模型-视图-节制器(MVC)设计模式中,节制器调用措施。在事故驱动情况中,视图经由过程事故处置惩罚法度榜样调用代码。这样,视图(即受控者)得到了节制器的角色。

我发明节制反转这个术语的包孕面太广。这就像把 Java 2 Platform, Enterprise Edition (J2EE) 开拓与软件开拓混在一路一样。确凿,J2EE 是 软件开拓,但它是软件开拓的高度专业版本。

幸运的是,Martin Fowler 已经站出来办理这个问题,他与多个框架的构建者相助,把这种形式的解耦命名为依附注入。这个术语好得多,由于它详细描述了开拓职员做的事情。以是,贯穿本文和下一篇文章,我都把这种形式的编码称为依附注入。假如您涉猎的其他资料应用 IoC,请确定知道作者指的是什么。

依附注入

彼此依附才能履行事情的软件组件,被当作是耦合在一路的。在软件开拓中,某种程度的耦合是弗成避免的。然则,该当只管即便把耦合节制在最小程度上。例如,在构建库代码时,最好把类型定义成接口,而不要定义成详细的类。这样,日后就可以在不改变库代码的环境下改动详细的类,由于库只依附接口中创建的定义。由于容器能够在运行时把组件注入到依附它的组件中,以是 DI 供给了打消组件间高度耦合的别的一种要领。请看 图 1 所示的例子。

图 1. 简单、原始的客户持久性框架

在这个例子中,Customer Workflow 类弗成避免地与 Customer Persistence 类耦合在一路,后者又绑定到数据库。表示这个架构的技巧术语是 yike!幸运的是,Java 天下的开拓职员避免了这种高度耦合。实际上,避免这类耦合是创建企业 JavaBean(EJB)技巧背后的一个念头。

假如换用接口来表示 Customer Persistence 类,可以实现一些代码分离。 图 2 显示了同一布局改进后的版本。

图 2. 应用接口对关系进行解耦

强制对接口进行依附,可以供给各类实现了接口的合约的实现类。在这种环境下,该当有一个 Customer Persistence 类读取 XML 文档,另有一个应用关系数据库。Workflow 类并不知道(或关心)详细采纳哪种机制。

深入 EJB 领地

跳过一些逻辑结论之后,经由过程 DI 的要领,经由过程接口、工厂、池(代理)工具和其他的 EJB 技巧,可以实今世码重用。图 3 展示了用范例的 EJB 关系描述的同一关系。

图 3. 用接口进行解耦的 EJB 版本

真的全都必要这样么?EJB 技巧当然供给了一层解耦,但价值是什么呢?EJB 规范的作者应用的是当时盛行的对象:承袭、接口和设计模式。他们也试图经由过程把问题放在框架中,从而办理开拓职员在编写企业利用法度榜样时会碰到的每个问题。图 3 中没有看到的是工具池、自动事务处置惩罚、安然性以及 EJB 技巧供给的所有其他好器械。惟一的问题便是我并不必要这些对象 —— 可 EJB 框架也包孕了它们。以是,原先简单的解耦问题现在成了大年夜问题。

您可以看到为什么对这种形式的解耦会有猛烈的否决。正如 Bruce Tate 在一篇 blog 文章中雄辩地指出:为了创建一个简单的利用法度榜样,“我没有需要吃掉落整头大年夜象”。规定性要领强制实现那些不必要的元素,因而这些要领弗成行。它们会在带来好处的同时带来很大年夜的繁杂性,而不论迟早,繁杂性会跨越人们以为会从框架中获得的好处。

经由过程 DI 解耦

从马后炮的角度来说,EJB 2.0 不是办理这些问题的精确措施。以是肯定有一种更简单的措施,可以在不添加不必要的包袱与繁杂性的环境下解耦利用法度榜样。当前办理这个问题的思虑依附于 DI。实际上,EJB 3 就采纳了这种要领,就像 Geronimo 一样(它逃避了所有重量级框架,以便实现更干净的技巧)。

在查看 Geronimo 以及它的 DI 实现要领之前,请看一个更简单的容器。以下示例应用 PicoContainer,这是 ThoughtWorks 开拓的一个开放源码的容器,除了履行 DI 之外什么也不做。这个容器显示了注入的自力事情要领,然后会转到 Geronimo 的事情要领和本系列中的第二篇文章。

构造函数注入和 PicoContainer

环抱着若何履行 DI,主要有两派思惟:构造函数注入 和 setter 注入。构造函数注入使用构造函数判断要返回的详细工具类型。Setter 注入经由过程 set() 措施注入类型。

在 PicoContainer 中,经由过程构造函数注入依附项。例如,图 2 显示的 Workflow 类必要经由过程持久性机制创建 customer 工具。对付这个示例,有两个查找器类:FinderFromFile 和 FinderFromDb,前者用 XML 文件进行搜索,后者则用关系数据库。虽然这个示例很小,但也应用了多个文件,归纳如表 1。

表 1. PicoContainer 示例中的文件

文件

类型

目的

CustomerLister

这个类应用一个查找器类型来按名称查找客户

FinderFromFile

CustomerFinder 接口的实现,它在文本文件中查找客户

CustomerFinder

接口

这个接口定义可以查找 Customer 工具的一些语义

Customer

搜索的主题;封装客户信息

CustomerWorkflow

利用法度榜样的节制器类,认真初始化容器。

TestCustomerListing

进行单元测试,履行客户的查找器

图 4 表示了这些类之间的关系。

图 4. PicoContainer 示例类之间的关系

代码

以下代码清单显示了 DI 在 PicoContainer 内的事情要领。

CustomerFinder

CustomerFinder 接口使 DI 成为可能。为了让 DI 事情,必须拥有一个接口,可以把这个接口的详细类注入到必要的行径的破费者中。在这个示例中,CustomerFinder 接口定义了措施 find(String name),如 清单 1 所示。

清单 1. 接口定义了若何查找客户

public interface CustomerFinder {

Customer find(String name);

}

这个接口定义了查找客户的语义,但没有公布履行实际搜索的细节。(根据应用 XML 文件照样数据库,细节会有所不合。)

FinderFromFile

FinderFromFile 详细类实现 FinderFromFile 接口。这个类如 清单 2 所示,认真按名称从文本文件中查找客户。

清单 2. 实现文本文件的 find(String name) 的类

public class FinderFromFile implements CustomerFinder {

private String fileName;

public FinderFromFile(String fileName) {

this.fileName = fileName;

}

public Customer find(String name) {

// . . . details omitted

}

}

CustomerLister

下面,定义认真调用查找器类的类 —— CustomerLister,如 清单 3 所示。便是这个类的依附项(也便是 CustomerFinder 接口应用的某个实现)被注入。

清单 3. 其查找器履行容器注入的类

public class CustomerLister {

private CustomerFinder finder;

public CustomerLister(CustomerFinder finder) {

this.finder = finder;

}

public Customer findCustomerByName(String name) {

return finder.find(name);

}

}

在 清单 3 中,可以看到 CustomerLister 的构造函数吸收一个实现 CustomerFinder 接口的类的实例。容器把这个实例注入这个 CustomerLister。这个行径在 DI 天下中称作构造函数注入,由于实例是经由过程一个构造函数通报的。

另一种(也是应用更广的)行径是 setter 注入,在这种注入中,经由过程 set() 措施注入依附类。PicoContainer 对这两种注入都支持,惟一真正的差别便是在依附类弗成用的时刻,是否容许用显式的依附构造类。这里的理论便是:假如不能注入依附项,那么就不能构建依附类。假如把理论撇开,这两类注入的功能实际没有差别。

清单 4 显示了应用 setter 注入时 CustomerFinder 接口的样子。

清单 4. 应用 setter 注入的 CustomerFinder

public class CustomerLister {

private CustomerFinder finder;

public CustomerLister() {

}

public void setFinder(CustomerFinder finder) {

this.finder = finder;

}

}

CustomerWorkflow

下一个要钻研的类是 CustomerWorkflow 类,在这个示例中它充当节制器。这个类在它的 configureContainer() 措施中设置设置设备摆设摆设 PicoContainer。这个措施然后再创建容器(充当单体)并注册组件和组件的参数。CustomerWorkflow 类如 清单 5 所示。

清单 5. 这个示例(CustomerWorkflow 设置设置设备摆设摆设容器)的节制器

public class CustomerWorkflow {

public MutablePicoContainer configureContainer() {

MutablePicoContainer pico = new DefaultPicoContainer();

Parameter[] finderParams =

{new ConstantParameter("customerListing.xml")};

pico.registerComponentImplementation(CustomerFinder.class,

FinderFromFile.class, finderParams);

pico.registerComponentImplementation(CustomerLister.class);

return pico;

}

}

留意,PicoContainer 的设置设置设备摆设摆设容许为注入的组件指定参数(在 Geronimo 中,这些组件被称作 GBean)。还请留意,设置设置设备摆设摆设是用 Java 代码编写的,而不是在 XML 文件(例如 Spring 容器)中完成的。假如乐意应用 XML,可以采纳 NanoContainer,这是一个开放源码的容器,与 PicoContainer 类似。不论应用哪个容器,现在已经设置了注入的依附项。现在只必要调用必要这些依附项并容许容器履行注入的代码。

TestCustomerFinder

问题的着末一部分是驱动进程的类。这个示例应用单元测试来验证每件事都按预期事情。TestCustomerListing 类(如 清单 6 所示)把所有这统统都绑在一路。

清单 6. 演示注入的测试用例

public class TestCustomerListing extends TestCase {

private CustomerWorkflow workflow;

public void setup() {

workflow = new CustomerWorkflow();

}

public void teardown() {

workflow = null;

}

public void testCustomerFinder() {

MutablePicoContainer pico = workflow.configureContainer();

CustomerLister lister = (CustomerLister)

pico.getComponentInstance(CustomerLister.class);

Customer foundCustomer =

lister.findCustomerByName("Homer");

assertEquals("Homer", foundCustomer.getName());

}

}

TestCustomerListing 类在 setup() 措施中创建 Workflow 类的实例,然后调用 configureContainer() 措施。然后这个类用 PicoContainer 把 CustomerLister 类 —— 这个类有精确的依附项(在这个示例中,是 FinderFromFile 查找器类)—— 提交给 lister 工具,后者会得到对指定客户的引用。

不用管移动部分的数量,这个示例演示了 DI 的解耦能力。为了创建一个维持客户的全新要领,仍旧可以经由过程扩展接口、对容器添加设置设置设备摆设摆设代码来找到客户。经由过程这种要领,就得到了曩昔只应用说话内置的 OOP 无法获得的解耦程度。

下期预报

理解像 DI 这样的主题时,艰苦之一便是这个主题与其他无关主题的耦合数量。例如,钻研 Geronimo 来进修 DI 就很艰苦,由于 Geronimo 中包孕许多移动的部分,但与 DI 毫无关系。在本文中,我把这个主题与实现分离,选择可以应用的最小软件栈来钻研 DI 的特性。我评论争论了 DI 的念头和定义,并用一个示例,演示了容器若何可以把依附项从一个组件注入到另一个组件。

在本系列的第 2 部分,我扬弃 PicoContainer 而转向 Geronimo,演示了在本文中事情的相同原则也适用于像 J2EE 利用办事器那样繁杂的情况。Geronimo 供给了与 PicoContainer 相同的解耦程度,然则还带有许多预先定义好的办事勾子,容许注入更繁杂的行径。

您可能还会对下面的文章感兴趣: