大多数项目不是属于 J2EE 应用程序就是属于 J2SE 应用程序。不过,有一些 J2EE 技术可以存在于 J2EE 容器之外,并且有些 J2SE 应用程序可以对它们加以利用。本月,Brian Goetz 分析如何在 J2SE 应用程序中使用某些 J2EE 服务。请在附带的讨论论坛上与作者和其他读者分享您关于本文的心得。(您也可以单击文章顶部或底部的讨论来访问论坛。)
在大多数情况下,Java 应用程序要么是 J2EE 应用程序、要么是 J2SE 应用程序,并且在这一点上是泾渭分明的。J2EE 应用程序需要 J2EE 容器的服务,容器要实现一长串的 J2EE API,包括 Enterprise JavaBean (EJB)、JTA、JNDI、JMS、JCA 和 JMX。J2EE API 设计为协同工作;究竟,J2EE 设计是从多年来数百人开发企业应用程序的经验中提取出的公共需求。像所有框架一样,J2EE API 的主要目的是“不重新发明轮子”。
有一些 API 属于 J2EE 规范的一部分,但是可以很轻易地在 J2SE 应用程序中使用,如 JDBC、jsp 和 servlet,但是对于大多数 J2EE API,J2EE 是一个要么是要么不是的命题——大多数 J2EE API 需要全功能的 J2EE 容器。不过,有一些服务器应用程序开发为 J2SE 应用程序而非 J2EE 应用程序,这通常都有很好的理由。为什么这些应用程序的开发人员必须重新发明轮子呢?J2EE 中是否有部分内容可以轻易地被 J2SE 应用程序借用来提供同样的优点呢?什么组件可以同时用于 J2EE 和 J2SE 应用程序的组件呢?
松散耦合
J2EE 的一个主要设计原理是 J2EE 应用程序可以松散地耦合——用组件组装,在组装或者部署应用程序时而不是在组件开发时定义或者改变这些组件的相互连接。J2EE 组件使用 JNDI 相互查找和查找所需要的资源,如 JDBC 和 JMS 连接。JMS 这样的技术鼓励松散耦合,答应灵活地为工作流程建模、轻易分配处理任务、可伸缩性和容错性。很多 J2SE 服务器应用程序也可以从这些技术和原理中受益。
像 JDBC、JMS 和 JNDI 这样的 API 基本上是“中间件”——它们作为应用程序与不同的服务提供者之间的统一接口。几乎每一个数据库服务器都有 JDBC 驱动程序,有大量的免费数据库服务器,所以几乎每一个希望利用数据库的 Java 应用程序都可以轻易地做到这一点。不过,对于 JMS 就不是这样了。消息队列服务器远没有数据库这样常见,非凡是在小型商店中。但是有大量的应用程序可以通过使用消息队列而极大地受益。
Somnifugi JMS
消息队列是一个功能强大的范例,它用于构建健壮的、灵活的、松散耦合的、可伸缩的应用程序。有一些商业消息队列产品,如 WebSphere MQ、Sonic、Fiorano、JBossMQ 和 SpiritWave。就像 JDBC 对于数据库一样,JMS 是消息的中间件——它使得应用程序可以通过统一的接口访问不同的消息队列产品,这个接口提供了像 Connection、Topic 和Message 这样的抽象。
许多 J2SE 应用程序使用某种形式的消息队列,但是不使用 JMS——而是使用线程池、工作队列、任务治理器等。AWT 和 Swing 框架使用消息(事件)在模型与视图层之间通信,JavaBean 组件利用监听器支持一种基于主题的消息。消息队列提供了很多结构上的优点——它固有的松散耦合有利于采用灵活的、基于组件的方法,并提供了有助于简化设计和减少互连的天然抽象边界。一个附带的好处是,MQ 范例使得分布式的、可伸缩的和容错的设计变得更轻易了,因为消息生产者和使用者不一定需要运行在同一 JVM 中,大多数生产者/使用者任务的本性是答应并发处理的。
J2SE 服务器应用程序的开发人员经常开发他们自己的消息层,或者从零开始,或者以 util.concurrent 这样的库为基础构建。通常这么做的理由是:
MQ 服务器是昂贵的和重量级的,并且由于我们不需要像持久性、分布式、事务和验证这样更重量级的功能,构建自己内存中的消息层,只提供所需要的功能会更轻易。
虽然这在一般情况下是正确的,但是应用程序需求通常会随着时间而增加,在开始时不需要的一些消息功能在以后可能会变得更重要了。
面向消息的应用程序的开发人员在开发过程的初期就必须做出选择——选择一个商业消息产品,或者构建自己的更便宜的、更轻量级的解决方案。Somnifugi JMS 包结合了这两种方式——一个基于高性能的 util.concurrent 库的非持久内存中消息队列服务,和一个符合 JMS API 的接口。与传统 JMS 提供者相比较,Somnifugi 是相当轻量级的,不管是功能上还是性能上。它只限于在一个 JVM 中使用(尽管可以取消这种限制),并缺少持久性、事务和验证功能。另一方面,它非凡快——它比传统 JMS 实现快得多,以至于可以在因性能原因可能无法使用消息的地方使用它。为了表明 Somnifugi 到底有多轻量级,在它的分发中包含了几个用 JMS 主题取代 Swing/JavaBean 事件框架的例子。
增加的灵活性
Somnifugi 还提供了另一项重要的优点:现在可以开发使用 JMS 接口的组件,然后在部署应用程序时决定是使用更快的、内存中的 Somnifugi 提供者还是更重量级的、但是更可靠的提供者,如 WebSphere MQ。可以将这种选择推迟到部署时的好处非常巨大——非凡是因为需求可能会在项目的开发过程中变化时——并提供了代码重用的机会,对于自已开发的消息层来说这是不太可能做到的。
在 J2SE 中使用 JNDI
像 JDBC 和 JMS 一样,JNDI 是一种中间件。像 JMS 一样,在 J2SE 应用程序中使用 JNDI 不像使用 JDBC 那么轻易。JDBC 提供者无处不在——有数十种兼容 JDBC 的商业和开放源代码数据库服务器。虽然所有 J2EE 容器都必须包括一个 JNDI 提供者,但是对于不属于 J2EE 容器的 JNDI,数量相对就少了。这不仅使在 J2SE 应用程序中使用 JNDI 更困难了,而且还意味着 J2SE 开发人员不太可能接触到 JNDI 并熟悉它的优势。
根据所使用的 JNDI 提供者和应用程序配置,JNDI 可能在 JNDI 名称空间中存储任意的 Java 对象(有一些限制:有些 JNDI 提供者限制存储的对象是可序列化的)。一般用 JNDI 存储静态配置数据(整型和字符串型)、JDBC DataSource 对象、JMS Connection 和 Topic 对象,以及无状态的对象(包括工厂对象)。完整地存储已配置对象,比如 JDBC DataSource 对象,而不只是配置数据,比如 JDBC URL,还可以增强应用程序的安全性,因为像授权凭证这样的敏感信息不能被应用程序直接使用。
J2EE 应用程序使用 JNDI 作为连接松散耦合组件之间的“开关板”——J2EE 组件使用 JNDI 寻找其他想要使用的组件,如 EJB 组件,并寻找 JDBC 和 JMS 连接这样的资源。J2EE 组件之间的互连是在组件的部署描述符中声明式地定义的,容器自动将对象绑定到名称空间中的特定位置,并保证在部署组件之前组件之间的所有资源依靠关系都得到满足。
J2SE 应用程序可以以类似于 J2EE 应用程序的方式使用 JNDI,只是它们必须多做一些填充名称空间的工作。但是好处是相同的——应用程序可以更松散地耦合,组件在运行时彼此发现。
免费 JNDI 提供者
虽然 JNDI 参考实现不包括一般性的 JDNI 提供者,但是可以下载 Sun 网站提供的 File System (FSContext)。这是一个示例 JNDI 提供者,它是以源代码的方式提供的,它访问并存储文件中的可序列化对象,还使名称空间的内容可以保证跨程序调用的一致性。虽然 FSContext JNDI 提供者主要是做为编写 JNDI 提供者的一个示例,但是简单的应用程序也可以使用它作为序列化对象的持久性数据存储,或者是作为“存根” JNDI 提供者,对从 JNDI 获得其配置的组件进行单元测试。
JBoss 开放源代码 J2EE 容器还包括一个更一般性的 JNDI 提供者 JNPServer,它可以轻易地作为单独的 JNDI 提供者运行,不需要 JBoss 容器。可以通过 RMI 从远程 JVM 访问 JNP,而在本地 JVM 中不会产生 RMI 开销。它在内部将对象存储到内存中的一个 HashMap 中。
在 JBoss 发行版的 jnpserver.jar JAR 文件中可以找到 JNP JNDI 服务器,它还依靠于 log4j 日志引擎。要使用它,必须配置 log4j,创建相应的 jndi.properties 文件(参见清单 1),并安排通过调用同一 JVM 或者另一个 JVM 中的 org.jnp.server.Main 的主入口点来启动服务器。访问 JNDI 名称空间的类文件在 JBoss 发行版的 jnpclient.jar JAR 文件中。
清单 1. JNPServer 的 jndi.properties
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
# Uncomment this line only if the JNDI server is to run in another JVM;
# otherwise, local JNDI requests will go over RMI
#java.naming.provider.url=localhost
Java 治理扩展(JMX)
Java 治理扩展(Java Management Extensions,JMX)是一种治理组件和服务的生命周期的机制。JBoss 大量使用 JMX——JBoss 中的几乎所有组件都作为 JMX 服务提供。结果就是很轻易配置一个只包括所需服务的应用程序。对于每一个组件服务,创建一个名为 MBean (托管的 bean)对象,它包含生命周期方法(start() 和 stop())和公开属性的 getter 和 setter。清单 2 显示了描述一个简单 Web 容器服务的 MBean 接口:
清单 2. 简单 Web 容器服务的 MBean 接口
public interface WebServerMBean {
// Lifecycle methods
void create() throws Exception;
void start() throws Exception;
void stop();
void destroy();
// Getter and setter for listener-port property
int getPort();