企业级 JavaBeans
随着 Web 应用程序变得越来越重要,开发者对他们的工具要求也越来越高。在过去的两个月里,我们研究了两种对象关系映射工具(Alzabo 和 DODS),它们使得可以使用对象方法来操作数据库,避免在程序内部使用 SQL。
但是,许多对象关系映射工具未能解决一些问题:如何将对象分离到不同的计算机上?一旦分离,对象之间如何互相发现?并且,如果对象的状态反映了一个或多个数据库行的状态,我们该如何处理事务?
这些是复杂且困难的问题,我们可以预见在未来几年内都将与它们抗争。对于这些问题(以及许多其他问题)最全面的答案之一是 J2EE(Java 2 企业版)平台及其企业级 JavaBeans 对象模型。EJB,正如它所知,专为复杂、大规模的网站而设计,并减少了程序员处理基础设施问题的需求。
本月,我们将开始研究 JBoss 应用服务器实现的 EJB。JBoss 根据 GNU Lesser General Public License (LGPL) 分发,不需要太多内存,并且相对容易使用。与 Java 编程项目通常的情况一样,使用 EJB 需要学习和使用一套新的工具,以正确的方式配置几个 XML 文件,并确保您的 CLASSPATH 在编译时和运行时包含正确的值。如果您可以克服这些后勤障碍,那么 JBoss 将为使用强大的服务器端技术奠定良好的基础。
在我们继续之前,重要的是要强调 EJB 和 Java 语言都不是真正的自由软件。虽然您可能不需要付费下载 Java 或 J2EE 库,但 Sun 拥有与 Java 相关的一切,包括规范。Sun 的社区源代码许可证比许多其他许可证更开放,但它远非开源许可证。
现在这一点尤其明显,因为赞助开源 Enhydra 应用服务器的 Lutris 公司已经停止了其 J2EE 认证的 Enhydra Enterprise 服务器。Lutris 已将 Enhydra Enterprise 转变为闭源项目,声称 Sun 的许可证使其无法交付完全兼容的开源 J2EE 服务器。主要的 Enhydra 邮件列表上充斥着愤怒(和辩解),并且仍然存在许多未解答的问题。也许 Lutris 在法律上(和财务上)有义务这样做,但他们这样做的方式是如何不关闭开源项目的一个例子。
幸运的是,JBoss 团队已经明确表示,JBoss 将继续是一个开源项目,并将继续发展并支持所有 J2EE 标准,即使它缺乏官方的 J2EE 认证,这主要是因为从 Sun 获得此类批准需要资金。
首先要问的一个好问题是,“我为什么需要 EJB?” 实际上,对于许多应用程序来说,EJB 是过度使用的。但是,EJB 提供了我们自己难以在服务器或容器内部实现的功能,正如它所知的那样。
EJB 可以与您的应用程序位于同一台计算机上,也可以位于远程计算机上。因此,您可以创建多层应用程序,其中每一层都位于不同的计算机上,并且当您将软件从一台计算机移动到另一台计算机或更改一个或多个层的配置时,您的软件可以继续不变地运行。
EJB 容器可以为您处理对象关系映射问题。您定义数据库表和映射到这些表的对象,容器可以处理其余的事情。或者,如果您喜欢自己微调,您可以让您的 bean 管理自己的持久层。
关系数据库提供事务,允许您将两个或多个操作视为单个操作。EJB 为您的对象提供了类似的事务能力,使得一个方法可以将多个操作作为一个整体执行。
同样重要的是要理解 EJB 不是什么;尽管名称相似,但企业级 JavaBeans 与普通的 JavaBeans 几乎没有任何关系。JavaBeans 具有标准 API,允许我们使用少量或不使用代码从 JSP 访问它们。相比之下,EJB 设计为可以从任何 Java 程序(包括 servlet)中使用。此外,EJB 的标准 API 比 JavaBeans 的更丰富、更复杂和更灵活。不幸的是,术语 JavaBeans 已被这两种流行的服务器端技术过度使用,但现在我们对此无能为力。
EJB 最令人信服的论据之一是 API 在应用程序服务器之间是标准的。因此,您可以开始使用开源 EJB 服务器(如 JBoss),然后在时机成熟时部署到商业服务器。(尽管一旦您了解商业服务器的成本,您可能需要重新考虑是否要放弃 JBoss。)
也许使用 Java 最令人恼火的部分是您必须记住的大量首字母缩略词、项目名称和版本号。本文使用 JDK(Java 开发工具包)1.3 和 JBoss 2.4.1a 服务器,它们实现了 EJB 1.1 标准。此外,虽然编写 EJB 类本身并不特别困难,但与编译和部署它们相关的后勤工作对于新手来说可能很烦人和困难。
将您的应用程序基于 EJB 意味着尽可能多地将业务逻辑移动到单独的对象中。在 EJB 中,这些对象有两种不同的类型:
实体 Bean 是映射到关系数据库的对象。实体 Bean 的每个实例通常对应于数据库表中的一行。每个实例变量对应于数据库表中的一列。我们通常需要在数据库中定义一个表来对应我们的实体 Bean,但 EJB 容器会根据我们的需要编写和执行 SELECT、INSERT、UPDATE 和 DELETE 查询。
会话 Bean 执行操作,可以单独执行,也可以使用一个或多个实体 Bean。会话 Bean 通常没有自己的状态,这使得它们比实体 Bean 更有效率。但是,有时在会话 Bean 中保留一些状态可能有所帮助。因此,EJB 提供了有状态会话 Bean,其状态在调用之间保持。
如果我们要使用 EJB 创建一个在线论坛,我们可能需要为用户、主题和帖子定义实体 Bean(以及相应的表)。我们还需要定义会话 Bean,以支持添加、修改和删除每种类型的实体 Bean,以及检索整个主题和单个帖子。
JBoss 是一个 Java 应用服务器,可以创建和部署多层 J2EE 应用程序。JBoss 并不声称处理应用程序方面的事情;为此,您需要使用 Jakarta-Tomcat 或其他 servlet 容器。但 JBoss 确实提供了后端功能,例如目录服务和消息服务,以及 EJB 容器。
假设您已经安装了 JDK,那么安装 JBoss 非常容易。Sun 提供了 RPM 格式的 JDK 副本,您可以从 java.sun.com 下载。您还需要下载并安装 Ant 实用程序,这是一个旨在取代古老的 UNIX make 程序的 Java 程序。如果您熟悉 make 和 XML,您会发现 Ant 的 build.xml 格式(如清单 1 所示 [可在 ftp.linuxjournal.com/pub/lj/listings/issue92/5497.tgz 获取])相对简单明了。
一旦您安装了 JDK 和 Ant,安装 JBoss 就变得轻而易举。我从 <j href=“http://boss.org” target=“_blank”>boss.org 下载了二进制代码,选择了集成的 JBoss 和 Jakarta-Tomcat 支持。该文件以 zip 压缩包的形式提供,这意味着您需要 Info-Zip 实用程序(我使用过的所有 Linux 发行版都附带)来解压缩它们。
解压缩后,JBoss-Jakarta 发行版包含两个子目录,分别命名为 jboss 和 tomcat。设置 JBOSS_DIST 环境变量指向 jboss 目录,以便各种与 JBoss 相关的实用程序和功能能够找到相应的文件。
此时,您可以使用以下两个命令启动 JBoss 服务器:
cd $JBOSS_DIST/bin sh run.sh
默认情况下,JBoss 会将大量信息记录到终端窗口。
我们的第一个 EJB 将是 Calculator,一个无状态会话 Bean,其 multiply() 方法接受两个整数并返回它们的乘积。在编写 Calculator 及其必要的 EJB 接口之后,我们将看到如何从独立的 Java 程序中使用它。
编写一个带有 multiply 方法的简单 Calculator 类通常不会非常困难。我们将创建文件 Calculator.java 并定义一个具有以下签名的方法:
public int multiply (int num1, int num2)
EJB 允许我们远程查找和调用我们的 Calculator Bean,这意味着我们必须编写几个类,以便能够找到 Calculator。最终,我们的应用程序将操作对实际 Calculator Bean 的远程引用,而不是对象本身。因此,编写会话 Bean 涉及编写一个 Java 类和两个接口。
Java 类是 bean 类本身,它执行实际的工作。bean 类不知道它已被另一台计算机上的对象调用;它可以通过查询其“上下文”来了解其环境,但通常不需要这样做太多。bean 类通常被称为 EJB 的简单名称,并附加单词 Bean。因此,我们的 Calculator EJB 的 bean 类是 CalculatorBean,在文件 CalculatorBean.java 中定义。bean 类必须实现 SessionBean 或 EntityBean 接口,具体取决于它是什么类型的 bean。
第一个接口是远程接口,它允许应用程序定位并获取对 Calculator EJB 的引用。远程接口传统上被赋予一个简单的名称,例如 Calculator,因此在文件 Calculator.java 中定义。远程接口应为 bean 类中的每个公共方法定义一个方法。远程接口必须扩展 EJBObject 类。
第二个接口是 home 接口,它允许 EJB 容器创建、定位、销毁和以其他方式管理企业级 JavaBean。home 接口传统上与远程接口具有相同的名称,并附加单词 Home。因此,我们的 EJB 的 home 接口的名称是 CalculatorHome,我们在文件 CalculatorHome.java 中定义它。home 接口必须扩展 EJBHome 类。
EJB 的优点之一是您的类在很多时候可以依赖默认的 EJB 行为。这可能不是最有效的方式,但它允许我们专注于编写代码的功能部分,让 EJB 容器处理几乎所有的基础设施。
现在我们了解了必须创建哪些类,我们可以开始编写一些代码。您会很快注意到,需要编写的代码不多,并且在我们的 CalculatorBean 类的情况下,我们的许多方法都定义了空主体。这是因为 CalculatorBean 继承的 SessionBean 接口强制我们定义这些方法,即使我们的 bean 足够简单以至于不需要使用它们。使用空方法主体履行了我们对接口的义务,同时保持了我们类的简洁性。
我将所有 Java 源文件放在 il.co.lerner.calculator 包中,这反映了它们来自我的商业域,并且这是计算器项目。因此,所有 .java 源文件都在目录层次结构 il/co/lerner/calculator 中。
我们的 bean 类 CalculatorBean(参见清单 2)定义了一个 multiply() 方法,该方法接受两个整数输入并向其调用者返回一个整数。除了实现 SessionBean 接口之外,CalculatorBean 实际上与 EJB 没有太多关系;实际上,它是一个相当枯燥的类,只有一个方法。我们写入 System.out 的任何内容都将打印到 JBoss 会话日志中。
清单 2. CalculatorBean.java,我们 EJB 计算器的 Bean 类
我们的 home 接口 CalculatorHome 允许我们创建 CalculatorBean 的新实例。除了定义接口的签名(包括它返回远程接口 (Calculator) 的实例这一事实)之外,home 接口非常简短。
package il.co.lerner.calculator; import java.io.Serializable; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBHome; public interface CalculatorHome extends EJBHome { Calculator create() throws RemoteException, CreateException; }
最后,我们的远程接口 Calculator 列出了 CalculatorBean 中每个公共方法的一个方法签名。
package il.co.lerner.calculator; import javax.ejb.EJBObject; import java.rmi.RemoteException; public interface Calculator extends EJBObject { public int multiply(int num1, int num2) throws RemoteException; }客户端程序将通过远程接口调用方法,而不是直接在 bean 上调用。远程接口和 bean 类的签名必须匹配,否则您稍后会遇到严重问题。
现在我们已经定义了它们,我们可以将我们的 Calculator 会话 Bean 部署到正在运行的 JBoss 服务器。部署我们的会话 Bean 意味着获取它的所有元素并将它们转换为单个 Java 归档 (jar) 文件。我们的 .jar 文件将包含 Calculator、CalculatorHome 和 CalculatorBean 的已编译类。
但是,它还将包含一个“部署描述符”,一个名为 ejb-jar.xml 的 XML 文件,它向 EJB 容器描述 .jar 文件的内容。部署描述符是 EJB 标准的强制性部分,并且在不同的应用程序服务器之间不会改变。它们告诉 EJB 容器我们选择的接口和类的名称,并允许我们定义诸如我们的 bean 将支持的事务类型之类的项目。我们的 Calculator EJB 的部署描述符在清单 3 中,应与 .java 源文件放在同一目录中。
清单 3. ejb-jar.xml,我们计算器 Bean 的部署描述符
我们的 .jar 文件还将包含一个名为 jboss.xml 的简短 XML 文件,我们将它与 ejb-jar.xml 放在一起。
<?xml version="1.0" encoding="UTF-8"?> <jboss> <enterprise-beans> <session> <ejb-name>Calculator</ejb-name> <jndi-name>calculator/Calculator</jndi-name> </session> </enterprise-beans> </jboss>
jboss.xml 文件是 JBoss 特有的,它将我们的 bean 绑定到 Java 的命名和目录接口 (JNDI)。有了 jboss.xml,向 JNDI 请求 calculator/Calculator 的客户端程序将获得对它的引用作为回报。
我们可以手动构建 .jar 文件,但使用 Ant 构建 .jar 文件并将其部署到正确的位置更容易。清单 1 [ftp.linuxjournal.com/pub/lj/listings/issue92/5497.tgz] 包含一个 Ant build.xml,它支持目标 ejb-jar(默认)和 deploy。如果您将 build.xml 放在 $CALCULATOR 中,那么您的 .java 文件、ejb-jar.xml 和 jboss.xml 应该在 $CALCULATOR/il/co/lerner/calculator 中。Ant 会将编译结果放在 $CALCULATOR/build/calculator 中,如 build.xml 中的 build.calculator.dir 属性所指定的那样。
在 $ANT 中安装 Ant 后,我们可以编译我们的 .java 文件,将它们转换为 EJB 兼容的 .jar 文件(ejb-jar.xml 文件位于强制性的 META-INF 目录中),并使用以下命令将其部署到 JBoss:
$ANT/bin/ant deploy
您应该在屏幕上看到许多描述编译和部署过程的消息。如果编译或构建失败,请检查您的环境变量是否设置正确,Java 文件是否没有任何语法错误,以及目录是否具有适当的权限。
如果您的 JBoss 服务器在您部署 Calculator .jar 文件之前已经在运行,您会注意到它会自动检测并部署该文件,而无需重新启动。这是 JBoss 的一大乐趣;为了部署您的 EJB .jar 文件,您只需将其复制到 $JBOSS_DIST/deploy 目录中。
现在我们已经部署了我们的 Calculator EJB,让我们编写一个简短的 Java 程序来使用它。清单 4 包含此类 UseCalculator.java 的源代码。
清单 4. UseCalculator.java,它连接并使用我们的计算器 EJB
虽然我们的程序完全独立于我们的 EJB 类,并且可以单独编译和运行(甚至在单独的计算机上),但我们使用 Ant 来跟踪 CLASSPATH(必须包括 JBoss 类以及来自我们的 .jar 文件的类),编译我们的代码,然后运行它。为了运行我们的应用程序,我们只需说:
$ANT/bin/ant use-calculator-ejb
这会在确保我们的 EJB 已编译、转换为 .jar 文件并部署后运行我们的程序。
UseCalculator.main() 写入 System.out 的任何内容(也称为 stdout 文件句柄)在我们运行 Ant 时都会打印在屏幕上。但是,我们的 CalculatorBean 方法写入 stdout 的任何内容都会打印到 JBoss 日志输出中。通过在一个终端窗口中保持 JBoss 打开,并在另一个终端窗口中运行 Ant,我们可以看到它们彼此通信。
UseCalculator 的 main() 方法包含连接和使用我们的 EJB 的几个标准步骤。我们首先连接到 JNDI,它跟踪当前部署到 JBoss 的对象。此连接称为上下文。我们的程序查找 jndi.properties,这是一个简短的 Java 属性文件,它告诉程序去哪里找到上下文(此文件应放在 $CALCULATOR/resources/ 中,如 build.xml 中指定的那样)。此文件采用 Java 资源格式,其中每行包含 name=value。
java.naming.factory.initial= java.naming.provider.url=localhost:1099 java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
一旦我们有了上下文,我们就使用我们在 jboss.xml 中给它的名称查找我们的对象,该名称位于我们的 ejb-jar.xml 内部。如果没有 jboss.xml,JBoss 将不会将正确的名称与我们的 EJB 关联,从而无法使用 JNDI 找到它。
JNDI 返回一个对象引用,然后我们将其强制转换为 CalculatorHome 的实例,然后使用该实例创建 Calculator 的实例。请注意我们如何创建 Calculator(远程接口)的实例,而不是 CalculatorBean 的实例。远程接口为我们提供了与服务器上 CalculatorBean 实例的透明连接,无论它在哪里。我们始终不知道 CalculatorBean 的真实实例位于何处。
最后,我们调用在 Calculator(远程接口)中定义的方法之一。我们的方法调用被传递给 CalculatorBean(bean 类),在那里执行(并打印一些日志信息)并返回(我们在那里将结果打印到 stdout)。
本月我们开始研究企业级 JavaBeans,这是一种使用 Java 创建分布式应用程序的基础设施。虽然 EJB 比 SOAP、XML-RPC 或其他分布式对象系统复杂得多,但它也被设计为处理更复杂的任务。(例如,SOAP 不尝试处理事务;这留给应用程序层来实现。)
与此同时,使用 Java 通常意味着花费更多时间在管理和后勤问题上,而不是在编程上。确定哪个文件必须在哪个目录中通常会令人沮丧,特别是如果您习惯于使用更动态的语言(如 Perl 或 Python)。然而,当您看到使用 EJB 可以多么轻松地创建分布式应用程序时,痛苦很快就会消退。JBoss 非常容易下载、安装和运行,并且内存占用非常小,这使得新手可以轻松尝试 EJB。
下个月,我们将继续使用 EJB,研究 EJB 的核心,即为我们的关系数据库提供对象接口的实体 bean。
电子邮件:reuven@lerner.co.il
Reuven M. Lerner 拥有一家小型咨询公司,专门从事 Web 和互联网技术。他与妻子 Shira 和女儿 Atara Margalit 住在以色列的 Modi'in。您可以通过 reuven@lerner.co.il 或 ATF 主页 www.lerner.co.il/atf 联系他。