扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
在学习示例应用程序之前,首先要详细了解一下将用于创建和部署该应用程序的三种技术。
Maven 是用于构建 Java 应用程序的应用程序,它从源代码一直构建到打包至 Web 站点。Geronimo 应用服务器是使用 Maven 应用程序的构建系统构建的。在它的核心内,Maven 的可扩展框架允许创建模块以执行构建软件组件时需要的一些动作。谈到 Apache Ant(Java 构建工具),Maven 的动作已经与使用 UNIX 命令 make 生成的结果进行了比较。Maven 脚本还允许将应用程序自动部署到正在运行的 Geronimo 服务器中。本文展示了如何使用 Maven 来将源代码打包到完整的 Java 2 Platform, Enterprise Edition (J2EE) 企业应用程序中。
Struts 是基于 Model 2 架构的 Web 应用程序框架。该混合架构最大程度地将业务逻辑和显示逻辑隔离。Struts 通过将业务逻辑隔离到纯 Java 类中来实现这一目标,纯 Java 类操作数据,并提供了丰富的标记库,该库可用于在编写 JavaServer Pages (JSP) 时显示数据。(在无数可用的 Web 开发框架中,许多框架都有很高的人气,比如 Tapestry 和 JavaServer Faces,而 Struts 一直是我偏爱的一个。)
XDoclet 起源于存在已久的 Java 文档工具 Javadoc。XDoclet 开发人员最初以一种新颖的方法使用 Javadoc,即使用专门的注释来生成可以被编译成源代码的模型。他们从 Javadoc 的实际使用抽身而出,生产出他们自己的变种,叫做 Xjavadoc。但是在 XDoclet 早期,仍然需要在源代码的 Javadoc 注释中使用标记来自动生产自动生成的代码。编写 J2EE 应用程序中的源代码和部署描述符文件可能十分冗长乏味。对于每一百行左右的 Java 代码,预计可以生成至少三倍多的支持 J2EE 描述符代码来完成工作。为了大大减少开发多层企业应用程序的痛苦,XDoclet 伸出援手,提供了代码标记和代码生成以使大部分部署描述符自动生成。这样就隐藏了 J2EE 的大量复杂性,但您要清楚代码生成器在构建什么,因为当事情没有如期进行时,您必须检查它。
部署计划
正如在简介中提到的,Maven 被 Geronimo 团队用来构建整个应用服务器。您可以利用 Maven 构建工具的强大功能来编译应用程序源代码,执行代码生成(需要 XDoclet 的帮助),绑定企业应用程序模块,并最终将其部署到正在运行的 Geronimo 服务器中。
该过程以三个文件开始。第一个文件 project.xml 定义什么是所谓的项目对象模型(Project Object Model,POM)。它列出应用程序的相关信息,包括应用程序名称、编写者、版本号、对构建应用程序非常重要的依赖关系,以及对如何构建应用程序的概述。出于本文目的,我们将主要研究 POM 的依赖关系部分。
上的指定资源库中下载构建应用程序所需的工件。iBiblio就是这样一个资源库,它包含数百个开放源代码 Java 库和支持文件,以及这些库的 POM 信息。它收集了大量信息,目的只有一个,即简化应用程序构建过程中 Java 开发人员的生活。电话簿应用程序 project.xml 文件的依赖关系部分由 23 个依赖关系组成,其中一半是 .jar 文件,用于支持 Struts 和 DisplayTag 标记库。其余的依赖关系主要是 XDoclet 要求。不使用 Maven,每个依赖关系都需要与示例应用程序绑定在一起。
Maven 只在第一次编译应用程序时下载所有依赖的工件。以后的编译运行利用 Maven 的本地资源库(已下载工件的本地高速缓存,通常位于 $HOME 目录的 .maven 目录下)来获得工件。
安装 Maven 1.0.2
我们的示例应用程序需要对 Maven 进行一些初始设置以使一切正确工作。首先,需要安装 Maven 1.0.2(参阅 参考资料 中的 Maven Web 站点链接)。安装完成后,在命令行输入 maven,将看到类似如下的信息:
E:\Documents and Settings\Neal\My Documents\eclipse\workspace\Phonebook> maven
__ __
| \/ |__ _Apache__ ___
| |\/| / _` \ V / -_) ' \ ~ intelligent projects ~
|_| |_\__,_|\_/\___|_||_| v. 1.0.2
编译 Geronimo
然后,按照 Wiki上的指令从源代码编译 Geronimo。成功构建之后,Geronimo 工件将位于本地资源库。它们是构建示例应用程序所必需的。编译完 Geronimo 之后,查找 geronimo-deployment-plugin-1.0-SNAPSHOT.jar 文件,并将其安装到 $MAVEN_HOME/plugins 目录中,否则可能会看到如下消息:
Tag library requested that is not present: 'geronimo:deploy' in plugin: 'null'
安装 XDoclet 1.2.3
最后,需要将 XDoclet 1.2.3 安装到 Maven 资源库中。如果试图构建示例应用程序,会显示一条 Maven 消息,指明它无法找到一些其他的 XDoclet 1.2.3 工件,这时您可能需要下载 XDoclet 1.2.3(lib 包),并将 .jar 文件解压到位于 .maven/repository/xdoclet/jars 的本地 Maven 资源库中。如果您是 Windows 用户,应该在 C:\Documents and Settings\username 目录下查找该 Maven 目录。如果您是 UNIX 用户,应该在主目录下查找该目录。还应该通过将 maven-xdoclet-plugin-1.2.3.jar 添加到 $MAVEN_HOME/plugins 目录来安装 XDoclet Maven 插件。
成功构建
具备这些先决条件之后,构建过程应该能够顺利进行。当然,可以在进行上述工作之前尝试构建,查看 Maven 找不到哪些文件,然后只安装这些文件。可以尝试在示例应用程序的顶层目录中运行 Maven。首先,许多工件将被下载,最后,您将看到 BUILD SUCCESSFUL 消息。
要更多了解 Maven 做什么,研究一下 maven.xml 和 project.properties 文件。Maven 是面向目标的。它读取 maven.xml 并尝试满足顶层项目元素的默认属性中指定的所有目标。在本例中,它尝试满足部署目标指明为先决条件的所有事项;也就是说,它将尝试构建 .ear 文件,然后尝试停止和启动应用程序。应用程序的部署是通过上述的 Geronimo Deployment Maven 插件执行的。当然,Geronimo 服务器应该运行 —— 否则构建将失败,并显示如下消息:
Failed to retrieve RMIServer stub: javax.naming.ServiceUnavailableException
[Root exception is java.rmi.ConnectException: Connection refused to
host: 10.0.0.7; nested exception is:
java.net.ConnectException: Connection refused: connect]
如果收到该消息,使用如下命令启动 Geronimo 服务器:
E:\geronimo-snapshot>java -jar bin\server.jar org/apache/geronimo/
DebugConsole org/apache/geronimo/RuntimeDeployer
该命令告诉 Geronimo 启动其服务器、DebugConsole 应用程序配置和 RuntimeDeployer 配置。
调试控制台
DebugConsole(参见 图 1)是可选组件,允许通过一个小 Web 应用程序(参阅 http://localhost:8080/debug-tool)查看 Geronimo 服务器中在运行什么。
图 1. Geronimo DebugConsole
示例顶层页面
构建完示例应用程序之后,可以访问它的顶层页面,您将看到如 图 2 所示的内容。
图 2. 使用 Struts 1.2.7 构建的 Geronimo Phonebook 示例应用程序
完成应用程序构建之后:底层内容
既然已经成功构建了应用程序,而且看到了使用 Maven 的强大功能和简单性,现在该学习使它完全运行所必需的文件布局和部分代码。首先介绍 Web 应用程序层,然后介绍 EJB 层。最后将查看结缔组织 —— 连接所有组件并让它完全工作的部署计划和配置文件。参考 图 3 所示的文件目录树,以便找到这些文件。
图 3. 示例应用程序文件的目录布局
Web 应用程序层
Web 应用程序是用 JSP 和 Struts 1.2.7 框架编写的。在 src/webapp 目录中将会找到组成示例应用程序 Web 接口的文件。
该应用程序由两个主要视图组成:电话号码列表和电话号码编辑屏幕。从数据库中编辑简单记录必需的所有特性(添加、删除、编辑、更新和列表)都存在。src/java/org/acme/phonebook/struts 目录包含大量 Struts 动作来执行这些必需功能,比如创建新条目,删除条目,列出所有条目,编辑现有条目。
Struts Tiles 模板系统用于确保花费在让 Web 应用程序外观正常上的工作最少。webapp/pages 目录中的 site-template.jsp 文件定义应用程序的外观。
接下来介绍的两个主要 JSP 是 EditPhoneNumberPage.jsp 和 ListPhoneNumbersPage.jsp。其中每个页面及其在应用程序中的功能在 清单 1 中说明。
清单 1. EditPhoneNumberPage.jsp
<%@ page language="java"%>
<%@ taglib uri="/tags/struts-bean" prefix="bean"%>
<%@ taglib uri="/tags/struts-html" prefix="html"%>
<%@ taglib uri="/tags/struts-tiles" prefix="tiles"%>
<tiles:insert page="/pages/site-template.jsp" flush="true">
<tiles:put name="content" type="string">
<hr>
<h1><bean:message key="h1.EditPhoneNumberPage" /></h1>
<hr>
<h2><bean:write name="phoneBookEntryForm" property="action"/>
</h2>
<html:form action="/pages/SaveEntry.do">
<table>
<tr>
<td>
<bean:message key="prompt.EditPhoneNumberPage.name" />
</td>
<td>
<html:text property="name" size="40" />
</td>
</tr>
<tr>
<td>
<bean:message key="prompt.EditPhoneNumberPage.phoneNumber" />
</td>
<td>
<html:text property="phoneNumber" size="40" /></td>
</tr>
<html:hidden property="action" />
<html:hidden property="pk" />
<tr>
<td></td>
<td>
<html:submit>
<bean:message key="button.submit" />
</html:submit> <html:reset>
<bean:message key="button.reset" />
</html:reset>
</td>
</tr>
</table>
</html:form>
</tiles:put>
</tiles:insert>
在 清单 1 中,前几行设置将在页面中处于活动状态的标记库。它们还将站点模板的内容区域设置为内容将显示的位置。本例展示了一个简单的基于 Struts 的输入屏幕,它用一些
如果查看 清单 2 中 SaveEntry.java 源文件中的类 Javadoc 标记,将会看到该应用程序中 XDoclet 标记的第一个示例。这些标记定义生成 Struts 部署描述符 struts-config.xml 必需的所有属性。
清单 2. SaveEntry.java Javadoc 类标记
/**
* Save an Entry
*
* @struts.action
* name = "phoneBookEntryForm"
* path = "/pages/SaveEntry"
* scope = "request"
* input = "/pages/EditPhoneNumberPage.jsp"
* unknown = "false"
* validate = "false"
* @struts.action-forward
* name = "success"
* path = "/pages/ListNumbers.do"
* redirect = "true"
*/
该示例代码位于一个 nutshell 中,展示了 /pages/SaveEntry 动作从 EditPhoneNumberPage.jsp 中获取输入,并使用 phoneBookEntryForm 将来自页面的用户输入打包到 Java 代码中。该动作完成之后,它重定向到 /pages/ListNumbers 动作以显示号码列表。
ListNumbers 动作位于 ListNumbers.java 文件中,它使用 清单 3 中的代码调用名为 PhoneBookSession 的 Session EJB。
清单 3. ListNumbers 动作的 execute() 方法的代码段
PhoneBookSessionLocal session =
PhoneBookSessionUtil.getLocalHome().create();
// Call the method
Collection c = session.listEntries();
// Put the retrieved information into the request attributes
// so the page can render them.
request.setAttribute("numbers", c);
在 清单 3 中,可以看到 PhoneBookSessionUtil 类的使用。它是一个 XDoclet 生成的类,用于帮助获得 PhoneBookSession 对象的主接口。创建了一个会话,调用了它的 listEntries() 方法,该方法返回所有电话簿条目的集合。然后请求对象中的 numbers 属性被设置为该集合。这样做的效果是将电话号码放到指定位置,以便用于显示条目的 JSP 可以检索并写出列表,如 清单 4 所示。
清单 4. ListPhoneNumbersPage.jsp 中的 DisplayTag
<display:table name="numbers" requestURI="ListNumbers.do"
scope="request" pagesize="5" id="row_obj">
<display:column property="name" title="Name"/>
<display:column property="phoneNumber" title="Phone"/>
<display:column title="Actions">
<logic:present name="row_obj">
<html:link action="/pages/EditEntry"
paramId="id" paramName="row_obj"
paramProperty="name">Edit</html:link>
<html:link action="/pages/DeleteEntry"
paramId="id" paramName="row_obj"
paramProperty="name"
onclick="return confirmDelete('Number')">
Delete
</html:link>
</logic:present>
</display:column>
...
</display:table>
EJB 层
该应用程序中有两个 EJB 类。第一个类使用容器管理持久性(Container-Managed Persistence,CMP)来提供对简单数据库表 PhoneBookEntryBean 的基于对象的访问。第二个类是一个 Stateless Session bean,它提供业务逻辑。通常需要通过无状态会话 bean 来操作 CMP bean,因为会话 bean 可被设置来提供对数据库的事务处理,从而在发生错误时可以回滚更新。此外,在 Session bean 中执行所有 CMP 操作使得 Web 应用程序无需知道数据库访问层的任何实现细节。所以如果用另一种技术替换该层(比如使用 Hibernate 持久层),将无需更改 Web 应用程序中的代码。
XDoclet 主要用在 EJB 层中以提供部署描述符生成。这对于减少构建此类应用程序所需的维护工作是十分重要的。下载源代码并查看 PhoneBookEntryBean.java 和 PhoneBookSessionBean.java 的类 Javadoc 注释,以了解用于定义 EJB 类的大量 XDoclet 标记。
要生成无状态会话 bean 的方法,添加名为 @ejb.interface-method 的 XDoclet 标记,其视图类型属性可以为 local、remote 或 both。该属性告诉 XDoclet 在会话 bean 的本地接口、远程接口或两种接口中生成相应方法。您还可以控制事务处理类型。参见 清单 5,它是其中一个接口方法的示例,列出电话簿条目并返回它们的值对象表示。
清单 5. PhoneBookSessionBean.java 类的 listEntries() 方法
/**
* List all of the phone book entries.
* @return a collection of PhoneBookEntryValue objects.
*
* @ejb.interface-method view-type="both"
* @ejb.transaction type="Required"
*/
public java.util.Collection listEntries() {
ArrayList values = new ArrayList();
try {
Collection entries = PhoneBookEntryUtil.getLocalHome().findAll();
Iterator i = entries.iterator();
while(i.hasNext()) {
PhoneBookEntryLocal entry = (PhoneBookEntryLocal)i.next();
values.add(entry.getPhoneBookEntryValue());
}
} catch (Throwable ex) {
ex.printStackTrace();
}
return values;
}
结缔组织
在我的文章“将数据库连接到 Geronimo 应用服务器的三种方法”(developerWorks,2005 年 6 月)中详细介绍了 Geronimo 的各种部署计划的重要性。让这么小的应用程序到达功能状态是非常有挑战性的,需要在部署计划中提供许多小选项,还有 XDoclet 标记之间的交互、标记生成的代码,以及部署计划。但是详细介绍这些内容超出了本文的范围。示例程序有许多配置文件和部署计划,几乎所有的这些东西都能在项目的 src/resources 子树中找到。下文简要介绍了这些文件的相关细节,以说明需要进行哪些修改才能让将来的应用程序工作。
ear 子目录包含企业应用程序部署描述符、application.xml 文件和 geronimo-application.xml 文件。在该应用程序中,这些文件被配置以提供应用程序范围的 Java 数据库连接 (Java Database Connectivity, JDBC) 连接器。
geronimo-application.xml 文件包含
清单 6. maven.xml 启动目标
<goal name="start">
<deploy:distribute
uri="deployer:geronimo:jmx:rmi://localhost/jndi/rmi:/JMXConnector"
username="system"
password="manager"
home="${basedir}"
module="target/${pom.artifactId}.ear"
/>
<deploy:start
uri="deployer:geronimo:jmx:rmi://localhost/jndi/rmi:/JMXConnector"
username="system"
password="manager"
id="org/acme/PhoneBook"/>
</goal>
资源目录中子目录列表中的下一个目录是 ejb 目录。openejb-jar.xml 部署描述符在 META-INF 子目录中。该文件对于数据库和实体 (CMP) bean 之间的所有连接是必不可少的。还必须对该文件进行小修改,以确保对于您的企业 bean 存在相应的 Java 命名和目录接口 (Java Naming and Directory Interface, JNDI) 名称。该文件如 清单 7 所示。
清单 7. openejb-jar.xml
<?xml version="1.0"?>
<openejb-jar
xmlns="http://www.openejb.org/xml/ns/openejb-jar"
configId="org/acme/PhonebookEJB"
parentId="MysqlDatabase">
<cmp-connection-factory>
<resource-link>MysqlDataSource</resource-link>
</cmp-connection-factory>
<enterprise-beans>
<entity>
<ejb-name>PhoneBookEntry</ejb-name>
<local-jndi-name>
java:comp/env/ejb/PhoneBookEntryLocal
</local-jndi-name>
<table-name>phone</table-name>
<cmp-field-mapping>
<cmp-field-name>name</cmp-field-name>
<table-column>name</table-column>
</cmp-field-mapping>
<cmp-field-mapping>
<cmp-field-name>phoneNumber</cmp-field-name>
<table-column>phone</table-column>
</cmp-field-mapping>
</entity>
<session>
<ejb-name>PhoneBookSession</ejb-name>
<local-jndi-name>
java:comp/env/ejb/PhoneBookSessionLocal
</local-jndi-name>
</session>
</enterprise-beans>
</openejb-jar>
清单 7 最重要的元素是 <cmp-connection-factory> 及其 <resource-link> 子元素。<resource-link> 元素内的名称必须与 JDBC 连接器配置的名称相匹配,如下列代码段所示:
...
<connectiondefinition-instance>
<name>MysqlDataSource</name>
...
openejb-jar.xml 中的 <local-jndi-name> 元素也是正常工作所必需的。如果收到找不到 JNDI 名称的错误消息,则可能错误设置了其中一个元素。
在 清单 7 中还需注意实体 bean 的
最后一个目录是名为 merge 的 src/resources 目录。该目录的内容与 XDoclet 代码生成的执行方式直接相关。XDoclet 从 Javadoc 标记中提取信息,但它还将信息合并到 merge 目录内专门命名的文件中。这些合并文件包含 Web 应用程序层的一些配置信息。在项目的 src/resources/merge 目录中,taglibs.xml 文件允许定义应用程序中的所有标记库。当 XDoclet 生成 web.xml 和 struts-config.xml 时,有许多文件合并到其中。
另一个部署计划存在于 src/webapp/WEB-INF/geronimo-jetty.xml 文件中,如 清单 8 所示。
清单 8. geronimo-jetty.xml
<?xml version="1.0"?>
<web-app
xmlns="http://geronimo.apache.org/xml/ns/web/jetty"
xmlns:naming="http://geronimo.apache.org/xml/ns/naming"
configId="org/acme/PhoneBookWeb"
>
<context-priority-classloader>true</context-priority-classloader>
</web-app>
清单 8 所示的这个部署计划有一个重要的行。如果不将
应用程序依赖的所有 .jar 组件绑定在 project.xml 依赖关系部分中指定,并由 Maven 自动处理。
最后一个配置文件是 project.properties,它驱动大多数构建处理,位于项目的根目录。该文件是一个仓库,存储 XDoclet 相关信息、有关在 .war 文件中包含哪些内容的信息,甚至还存储控制 Java 编译器输出格式的属性。
将所有这些元素放在一起要花费大量时间,但希望您能够使用该示例应用程序作为您前行的垫脚石。
结束语
本文为更大的应用程序提供了非常基本的起始模板。为业务逻辑添加更多 CMP bean 和会话 bean,可以创建一个更有趣的 Web 应用程序。我已经展示了 Maven 构建系统的主要优点,即降低编译和生成最终 .ear 文件的嵌套文件结构的复杂性。XDoclet 代码生成系统还用于简化生成 EJB 工件、Struts 工件和 Web 应用程序工件的过程,这些工件是编译最终电话簿应用程序所必需的。使用该示例应用程序,您现在应该能够利用 Maven 和 XDoclet 来简化您的企业应用程序开发过程。
Geronimo 开发人员已经按照 Sun J2EE 规范认真构建了服务器本身,并生产出一种将部署和配置问题与细节严格分离的产品,而这是用容器托管应用程序所必需的。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者