业务需要重量级架构(比如 Enterprise Java™Beans (EJB)技术)提供的企业服务,但这种架构对于解决日常问题可能过于复杂。本文介绍轻量级容器,并解释它们如何提供满足您业务需要的服务,而无需将您束缚在一个指定的编程模型之上。
我是一名狂热的山地车手。许多山地车手非常偏爱核心设备 —— 巨兽(behemoth),这是一种全悬挂系统(拥有前后两个巨大的减震器)——
简直到了无以复加的地步。我在德克萨斯州奥斯汀附近的丘陵地区骑车,那里的环境要求我必须有一辆悬挂式山地车,但巨兽有些地方并不适合。我不可能带着多余的负重攀岩。我必须有一辆轻便的山地车。
Java 行业经历了类似的情况。EJB 技术提供了核心的企业服务。如果您曾对一个复杂的、基于组件的模型编程,您会将业务组件放入一个容器中,该容器提供诸如事务、远程控制、安全和持久性之类的服务。
然而,这里存在一些开销。重量级架构对于解决许多问题都过于复杂。例如,实体 bean 会让您为每个实体编写 7 个文件。因此 EJB
技术就不值得用来解决日常问题。如今,许多业务仍然需要企业服务,但它们正在寻找达到该目标的新方向。它们使用轻量级容器。实际上,最新的
EJB V3.0 标准就使用了轻量级容器模型。
大多数容器 API(如 EJB API)强迫您编写一些接口或一个组件模型。将您的组件放入该容器后,容器会为您处理一些事情。EJB
容器提供企业服务。Servlet 容器(例如 Apache Jakarta Tomcat)实现了 Servlet API,使您可以将动态内容建立到服务器页面中,该页面随后会被发送到
Web 浏览器。
传统容器强迫使用指定的编程模型,轻量级容器则不是。它们使用普通 Java 对象(plain old Java object,POJO)。容器然后将
POJO 绑在一起,并将服务与它们相关联。轻量级容器的共同特征包括:
- 基于 POJO 的编程 —— 轻量级容器不具侵犯性。它不强迫执行任何 API。
- 生命周期管理 —— 轻量级容器管理放入其中的对象的生命周期。最低限度下,它们实例化并销毁对象。
- 依赖性解析 —— 轻量级容器提供了一个普通的依赖性解析策略。多数容器现在支持称为依赖注入的策略。还有一些支持
Java 2 平台企业版(J2EE)风格的策略,称之为服务定位。
- 一致的配置 —— 轻量级容器是一个便于提供一致配置服务的位置。
- 服务关联 —— 轻量级容器提供一种将服务与容器中的对象相关联的方法。
优点
轻量级容器有许多胜于其他容器架构的优点。例如,您可以使用一个更加简单、基于 POJO 的编程模型。使用 POJO 编程,应用程序会更加易于测试。您的对象也可以在容器外运行
—— 例如,在一个测试用例中。通过依赖注入,轻量级容器减少了组件间的依赖性。它们也保护了您在代码上花费的心血,因为您可以在容器间移动应用程序的大部分。
革新浪潮引发了轻量级容器运动。依赖性管理在这场运动中吹响了第一声号角。早期轻量级容器,比如 Apache 的 Avalon,使用服务定位策略来管理依赖性。现代大多数容器使用依赖注入管理依赖性。
然而,解析依赖性还只是问题的一部分。您还需要可以将服务与容器中的 POJO 相关联。EJB 容器使用代码生成处理该问题。现代容器使用面向方面编程(AOP)和拦截。
依赖注入
在 Java 技术中,依赖注入正迅速地改变我们构建应用程序的方式。这种概念相对简单:一个消费者(类似下面的 Consumer
类)需要一个服务。您为指向该服务(类似下面的 Speaker 类)的消费者添加一个属性。清单 1 展示了此概念的示例。
清单 1. 依赖注入的示例
class Speaker {
void speak(String words) {
System.out.println(words);
}
}
class Consumer {
Speaker mySpeaker;
void saySomething() {
mySpeaker.speak("something");
}
}
|
请注意 Consumer 类。它没有实例化 Speaker 类。有了依赖注入,该工作就由称之为
Container 类(参阅清单 2)的第三方来处理。
清单 2. Container 类
class Container {
public static void main(String[] args) {
Speaker speaker=new Speaker();
Consumer consumer=new Consumer();
consumer.speaker = speaker;
consumer.saySomething();
}
}
|
Container 类实例化了 Speaker
和 Consumer 类。然后 Container
类将 speaker 属性设置为新的 Speaker 类。最后一步表示依赖注入。
让我们稍微对该段代码进行重构。构建一个称为 Speaker 的接口和两个不同的实现:一个 Canadian speaker
和一个 Californian speaker。因此,您现在拥有了 Speaker 接口:
interface Speaker {
void speak(String words);
}
|
CanadianSpeaker 和 CalifornianSpeaker:
class CanadianSpeaker implements Speaker {
public void speak(String words) {
System.out.println(words + ", ay?");
}
}
class CalifornianSpeaker implements Speaker {
public void speak(String words) {
System.out.println("Like, " + words);
}
}
|
还有容器中的单行更改:
... Speaker speaker=new CalifornianSpeaker(); |
注意,您现在可以在 speaker 的两个实现之间变化,而惟一需要改变的代码就是容器。更关键的是,您可以轻松地注入模拟对象来替代真正的
Speaker 实现,并且无需影响其他的代码基就可以进行测试。
当然,最终目标是用专门定制的容器来替代这个手写容器。例如,使用 Spring 容器。在本例中,替换的是您的 Container
类,并且您可以使用类似清单 3 中代码的简单 XML 文件。
清单 3. 用于 Spring 容器的 XML 文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="mySpeaker" class="CanadianSpeaker">
</bean>
<bean id="consumer" class="Consumer">
<property name="speaker"><ref local=
"mySpeaker"/></property>
</bean>
</beans>
</code>
|
现在,您可以下载上下文,获取 Consumer bean,然后像下面这样运行它:
ApplicationContext ac =
new FileSystemXmlApplicationContext("context.xml");
Consumer c=(HelloWorld)ac.getBean("consumer");
c.saySomething();
|
Spring 容器与您的容器完成相同的事情。它实例化 beans,并通过设置属性而将它们绑在一起。注意,两部分代码是完全去耦的;接口和容器确保了这一点。您可以使用依赖注入来满足进行企业级开发(例如,数据源或事务管理器)所遇到的许多依赖性。
您也会看到,应用程序的固有层次之间自然地相互依赖。您可能拥有一个由控制器调用的 Web 用户界面(UI)视图,它调用外观层,外观层调用数据访问对象,数据访问对象调用对象关系映射,对象关系映射调用数据库。这些关系就是依赖性。如果能将它们解耦,会更加易于编码、测试和维护。
依赖注入让您将应用程序的主要层次编织到一起,从而,这使您产生一个具有视图、模型和控制器层的松散耦合应用程序。但是,轻量级容器解决了另一个问题。您经常具有一些会影响到应用程序许多部分的关注点,比如日志记录、远程控制或安全性。EJB
通过使用代码生成和容器/组件接口解决了该问题。我们有能力做到更好。
您可以在合适的位置编写自己的横切关注点(crosscutting concern),然后使用称为拦截(interceptor)的技术将关注点绑定到需要它的方法上。比如说,一个调用者想要调用称为
speak() 的方法。使用拦截策略(参阅图 1),您使用 speak()
方法在目标对象前创建了一个代理。该代理应有一个 speak() 方法,该方法与原始目标对象拥有相同的接口。当调用者调用代理时,您可以在调用真实方法前,轻松添加您需要的任何定制特性。
图 1. 轻量级容器的主要拦截策略
通过使用拦截,您可以更加高效地添加类似安全、声明性事务和远程控制这些自定义服务到 POJO 方法。需要附加说明的是:调用代码和被调用方法都不需要变更。而且,类似
Spring 的容器都预装了拦截来执行这些任务和其他任务。
AOP 更进了一步。使用 AOP,您能够立即快速指定所有需要给定服务的方法,通常是使用正则表达式来做到这一点。AOP 程序员称这一套方法为切点(point
cut)。例如,您可能想将将声明性事务与外观中的所有方法相关联。对于以 insert 或 update
单词开始的方法,您可能想要完整的事务传播,而对其他方法,您想要轻量级的只读传播。(EJB 规范定义了事务传播的类型。您只需知道,完整的事务传播的开销要大于只读传播,但是它也更加安全,并且对于某些类型的更新是必需的。)
在正确的位置得到正确的事务行为,无需修改调用者或目标方法中的任何代码。清单 4 展示了部分 Spring 上下文,它为这样的应用程序指定了切点。
清单 4. Spring 容器中指定切点的代码
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
|
您可以想像得到,许多人都在努力构建轻量级容器。轻量级容器空间围绕一些参与者,比如 Spring、Pico、HiveMind 和
EJB 技术,正迅速发展壮大。
Spring
处于领先地位的是 Spring Framework。Spring 使用 XML 配置并且依赖于 setter 进行依赖注入。它也使用构造函数和工厂方法,但您会看到的大多数示例和
Spring 代码基本身还是使用 setter。
Spring 通过使用胶水代码添加大量的 bean,从而超越了轻量级容器。使用 bean 和 代码,您可以插入数百种让您使用
J2EE API 的不同组件,从 JDO 到 Hibernate 的持久性引擎,工作流引擎,视图技术,等等。Spring 正在快速成熟,并将在可预见的未来成为一个参与者。
Pico
Pico 容器是最小的可用容器。它与 Spring 的主要区别之处是在风格上面。Pico 程序员首先依靠构造函数进行注入。Pico
不使用 XML,而是使用 Java 代码来注册容器中的 bean。类似 Spring,Pico 也支持 setter 注入,但主要使用构造函数。Pico
也远不及 Spring 所支持的服务数量。它主要是一个依赖注入容器。不过,当您不需要 Spring 提供的所有企业服务时,Pico
绝对是您的首选。
HiveMind
HiveMind 是最新的开放源码轻量级容器。它比 Pico 拥有更多的支持模块,但还是少于 Spring。尽管如此,HiveMind
使您可以使用 Spring 的一些 bean 和 服务。它能通过 setter 和构造函数管理依赖性。HiveMind 的主要贡献是以下两个重要思想:
- 称为模块的封装概念,松散地基于 Eclipse 插件模型,使您在粗粒度级别管理依赖性。
- 称为 HiveDoc 的文档工具让您为容器中的模块生成参考文档,这非常类似于使用 Javadoc 为您的 Java 代码生成文档。
现在说 HiveMind 是否会产生大的影响还为时过早。
EJB 技术
考虑您现在使用 EJB 所做的事情,例如声明性事务、远程控制和安全性。如果您可以使用轻量级容器代替在 EJB 环境中所做的大多数事情,那为什么不使用轻量级容器呢?该问题迫使
EJB V3.0 专家组重新设计核心 EJB 架构。EJB V3.0 将更有效地实现轻量级容器策略。当前 EJB 架构和最新的
EJB V3.0 间的主要区别是:容器将提供您使用的主要服务,并且您可以使用 XML 来配置容器,但 EJB 技术也将严重地依赖配置的注释。
一些优秀的顾问已经对 EJB 技术中注释的过度使用引起了关注。我也保留我的意见,但 EJB 技术确实有了大麻烦。EJB 小组必须尽快发布补丁,否则轻量级容器可能会放弃使用
EJB 技术。客户已经可以使用 Spring 满足绝大多数的需要。在 EJB 技术大量使用之时,时机可能过晚了。
现在,您已经大概了解了轻量级容器。您了解了以下的基本设计理论:
- 构建一个接受 POJO 的容器,而不是受限制的组件
- 使用依赖注入来松散、解析依赖性
- 使用拦截和 AOP,将服务与 POJO 相关联
在接下来的文章中,我将详尽地为您介绍主要的轻量级容器并说明何时使用它们。然后,我将比较完整地介绍 Spring(最流行的容器)。在本文开始的部分,我告诉您我需要在不牺牲自行车避震系统的前提下,减轻自行车的重量。现在您知道了,如何在不牺牲企业服务的前提下减轻您的容器。
学习愉快!
|