级别: 中级 Kunal Mittal (kunal@kunalmittal.com), Java 和 Web 服务专家, 顾问
2006 年 3 月 02 日 学习如何在 Apache Geronimo 上部署 Java™ 服务器页面(JSP)、servlet 和不同的企业 JavaBean(EJB)。这篇文章包含 Apache Geronimo 需要的部署步骤,这与其他 Java 2 平台企业版(J2EE)容器不太一样。
学习在运行 Apache Geronimo 的计算机上部署简单 J2EE 应用程序的步骤,了解这些步骤与其他符合 J2EE 规范的应用服务器的相似之处。虽然这篇文章主要侧重于如何在 Apache Geronimo 上部署 EJB,但也会学到关于 JSP 和 servlet 方面的内容。这篇文章假设读者知道如何在其他 J2EE 容器上编写和部署 EJB,例如 IBM WebSphere® 应用服务器、JBoss 或 WebLogic。
注: 请使用 Apache Geronimo 的当前发行版(在编写这篇文章时,是版本 1.0 M5)处理这篇文章中的代码示例。
 |
J2EE 部署过程
Apache Maven 用来构建和部署这篇文章中的全部示例代码。所以,有许多文件是特定于 Maven 的构建脚本。输出结果是企业档案(EAR)文档。在 .ear 文件构建完成后,必须运行以下命令在 Apache Geronimo 上部署它:
$ java -jar bin/deployer.jar deploy phonebook.ear
|
|
我要感谢 Neal Sanche 允许我使用他的文章 “利用 Geronimo 深入 EJB Web 应用程序”(developerWorks,2005 年 7 月)中的代码作为解释部署过程的基础。在阅读本文时,请下载代码并参考它(请参阅 下载 一节)。
JSP 和 servlet
JSP 和 servlet 是驱动 J2EE 应用程序的用户界面(UI)层的两种基本 J2EE 技术。JSP 主要用于表示逻辑和 HTML 代码。Servlet 构成典型的模型-视图-控制器(MVC)体系结构的控制器层,并充当表示层和模型层的接口。
示例代码中的简单应用程序是用 Apache Struts 编写的。代码包含少量 Struts 动作类和两个 JSP 页面。图 1 显示了源代码的结构。
图 1. 示例的源代码结构
Struts 动作类在 Phonebook/src/java/org/acme/phonebook/struts 目录中。JSP 页面在 Phonebook/src/webapp/pages 目录中。
这个示例中唯一一个真正的 servlet 是 Struts 动作 Servlet,它控制动作类的调用。请在代码树中找到 servlet-mappings.xml 和 servlets.xml,并查看 Struts 动作 Servlet 的声明方式。这些文件的代码分别显示在 清单 1 和 清单 2 中。
清单 1. Servlet-mappings.xml
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!-- Session Config -->
<session-config>
<!-- Make sessions last two hours -->
<session-timeout>120</session-timeout>
</session-config>
|
清单 2. Servlets.xml
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>
org.apache.struts.action.ActionServlet
</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>
/WEB-INF/conf/struts-config.xml
</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>0</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
|
对于 J2EE Web 程序员来说,这个代码很熟悉。servlet-mappings.xml 和 servlets.xml 文件在 Maven 构建期间被合并到 J2EE Web 档案(WAR)文件的 web.xml 文件中。请执行构建过程并查看生成的 web.xml 文件。
EJB
Apache Geronimo 用 OpenEJB 作为 EJB 容器系统。示例包含两个 EJB:
容器管理的实体 EJB
Neal 在他的文章中解释说,在他的 EJB 中非常多地利用了 XDoclet。XDoclet 像 EJBGen 一样,会替您生成大部分 EJB 代码,包括部署描述符。
XDoclet 的概念与 Java 标注类似。Javadoc 样式的注释被用作 XDoclet 编译器的触发器,并生成许多必要的代码。请浏览 清单 3 所示的 PhoneBookEntryBean.java,查看 XDoclet 使用的标注样式。
清单 3. PhoneBookEntryBean.java
package org.acme.phonebook.ejb;
/**
*
* @ejb.bean
* type="CMP"
* cmp-version="2.x"
* name="PhoneBookEntry"
* local-jndi-name=
* "org.acme.phonebook.ejb/PhoneBookEntryLocalHome"
* view-type="local"
* primkey-field="name"
*
* @ejb.finder
* signature="java.util.Collection findAll()"
* query="SELECT OBJECT(o) from PhoneBookEntry AS o"
*
* @xx-ejb.data-object
* container="true"
* setdata="true"
* generate="true"
*
* @ejb.value-object
*
* @ejb.transaction type="Required"
* @ejb.permission unchecked="true"
* @struts.form include-all="true"
*
* @web.ejb-local-ref
* name="ejb/PhoneBookEntryLocal"
* type="Entity"
* home="org.acme.phonebook.ejb.PhoneBookEntryLocalHome"
* local="org.acme.phonebook.ejb.PhoneBookEntryLocal"
* link="PhoneBookEntry"
*
* @ejb.persistence table-name="PhoneBookEntry"
*
*/
public abstract class PhoneBookEntryBean
implements javax.ejb.EntityBean
{
/**
*
* @ejb.pk-field
* @ejb.persistence
* column-name="NAME"
* jdbc-type="VARCHAR"
* sql-type="VARCHAR(250)"
*
* @ejb.interface-method view-type="local"
*
*/
public abstract java.lang.String getName();
/**
* @ejb.interface-method view-type="local"
*/
public abstract void setName(java.lang.String newValue);
/**
*
* @ejb.persistence
* column-name="PHONE_NUMBER"
* jdbc-type="VARCHAR"
* sql-type="VARCHAR(250)"
*
* @ejb.interface-method view-type="local"
*
*/
public abstract java.lang.String getPhoneNumber();
/**
* @ejb.interface-method view-type="local"
*/
public abstract void setPhoneNumber(java.lang.String newValue);
/**
* @ejb.interface-method
*/
public abstract org.acme.phonebook.ejb.PhoneBookEntryValue
getPhoneBookEntryValue();
/**
* @ejb.create-method
*/
public java.lang.String ejbCreate(java.lang.String name,
java.lang.String phoneNumber)
throws javax.ejb.CreateException
{
setPhoneNumber(phoneNumber);
setName(name);
return null; // should not return primaryKey for CMP:
}
public void ejbPostCreate (java.lang.String name,
java.lang.String phoneNumber)
throws javax.ejb.CreateException
{
}
/**
* This is a create method which takes only the value of the
* primary key, because this object does not have automatic
* key generation turned on.
*
* @ejb.create-method
*/
public java.lang.String ejbCreate(java.lang.String name) throws
javax.ejb.CreateException {
setName(name);
return null;
}
public void ejbPostCreate(java.lang.String name)
throws javax.ejb.CreateException {
}
}
|
在 清单 3 所示的 PhoneBookEntryBean.java 中,进一步观察 Javadoc 注释。在这个代码中,Javadoc 注释更有趣,包含比代码本身更多的信息。标注定义了 EJB 的类型、容器管理的字段、发现器查询、数据类型和其他在 EJB 部署描述中通常会出现的信息。另外,不需要编写 home 接口、remote 接口和 local 接口类。Maven 和 XDoclet 在构建过程中会在后台把这些工作全做了。
使用 OpenEJB 容器系统所需要的基本部署描述符是 openejb-jar.xml,如 清单 4 所示。这个描述符与 PhoneBookEntryBean.java 的 Javadoc 标注组合在一起,最终构成了 ejb-jar.xml 和符合标准 J2EE 规范的 EJB 应用程序所需要的其他描述符。
清单 4. 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>
<jndi-name>PhoneBookEntry</jndi-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>
<jndi-name>
org.acme.phonebook.ejb/PhoneBookSession/Home
</jndi-name>
<local-jndi-name>
java:comp/env/ejb/PhoneBookSessionLocal
</local-jndi-name>
</session>
</enterprise-beans>
</openejb-jar>
|
注:
Aaron Mulder 编写的 Apache Geronimo Development and Deployment 一书中有这个 XML 文件结构的精彩可视表示。(请参阅 参考资料 获得这本书的链接。)
基本上,这个文件:
- 定义在容器中部署的实体 bean。
- 设置 Java 命名和目录接口(JNDI)名称。
- 声明该 bean 是实体 EJB。
- 定义使用的数据源。
- 定义这个实体 EJB 代表的表的名称。
- 用数据库表中对应的列,对代表列的容器管理字段进行定义。
这就够了!使用一个简单的 Java 类和这个部署描述符,就完成了部署实体 EJB 需要做的全部工作。在过去,编写实体 EJB 至少需要一到两天。现在,只要两个小时。
无状态会话 EJB
在部署时,会话 EJB 需要的工作实际上与实体 EJB 相同。实际上,在这里 XDoclet 的使用方式也与前面用于实体 EJB 的方式类似。清单 5 所示的 PhoneBookSessionBean.java 显示了处理会话 EBJ 时需要的部署描述符的基本情况。它实际上比实体 EJB 的部署描述符简单得多。
清单 5. PhoneBookSessionBean.java
package org.acme.phonebook.ejb;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* Phone book session bean. A place for all of the useful business
* methods that can be done with phone book entries.
*
* @ejb.bean name="PhoneBookSession"
* type="Stateless"
* local-jndi-name="java:comp/env/ejb/PhoneBookSessionLocal"
* jndi-name="org.acme.phonebook.ejb/PhoneBookSession/Home"
* view-type="both"
*
* @ejb.permission unchecked="true"
*
* @ejb.interface generate="local,remote"
* remote-class="org.acme.phonebook.ejb.PhoneBookSession"
* local-class="org.acme.phonebook.ejb.PhoneBookSessionLocal"
* @ejb.home generate="local, remote"
* remote-class="org.acme.phonebook.ejb.PhoneBookSessionHome"
* local-class="
* org.acme.phonebook.ejb.PhoneBookSessionLocalHome"
* @ejb.util generate="physical"
* @ejb.ejb-ref ejb-name="PhoneBookEntry" view-type="local"
* ref-name="ejb/PhoneBookEntryLocal"
* @web.ejb-local-ref
* name="ejb/PhoneBookSessionLocal"
* type="Session"
* home="org.acme.phonebook.ejb.PhoneBookSessionLocalHome"
* local="org.acme.phonebook.ejb.PhoneBookSessionLocal"
* link="PhoneBookSession"
*
*/
public abstract class PhoneBookSessionBean implements
javax.ejb.SessionBean {
/**
* Add a phone book entry.
* @param name the name
* @param number the number
*
* @ejb.interface-method view-type="both"
* @ejb.transaction type="Required"
*/
public void addEntry(java.lang.String name,
java.lang.String number) {
try {
PhoneBookEntryLocal entry =
PhoneBookEntryUtil.getLocalHome().
create(name,number);
} catch(Throwable ex) {
ex.printStackTrace();
}
}
/**
* Delete a phone book entry for the given name
* @param name the name to remove
* @ejb.interface-method view-type="both"
* @ejb.transaction type="Required"
*/
public void deleteEntry(java.lang.String name) {
try {
PhoneBookEntryLocal entry =
PhoneBookEntryUtil.getLocalHome().
findByPrimaryKey(name);
entry.remove();
} catch (Throwable ex) {
ex.printStackTrace();
}
}
/**
* Update a phone book entry for the given
* name with the given number.
* @param name the name
* @param number the phone number
* @ejb.interface-method view-type="both"
* @ejb.transaction type="Required"
*/
public void updateEntry(java.lang.String name,
java.lang.String number) {
try {
PhoneBookEntryLocal entry =
PhoneBookEntryUtil.getLocalHome().
findByPrimaryKey(name);
entry.setPhoneNumber(number);
} catch (Throwable ex) {
ex.printStackTrace();
}
}
/**
* Find an entry by name.
* @param name the name to look up.
* @return a phone book entry value representing the entry.
*
* @ejb.interface-method view-type="both"
* @ejb.transaction type="Required"
*/
public PhoneBookEntryValue findEntry(java.lang.String name) {
try {
PhoneBookEntryLocal entry =
PhoneBookEntryUtil.getLocalHome().
findByPrimaryKey(name);
return entry.getPhoneBookEntryValue();
} catch (Throwable ex) {
ex.printStackTrace();
}
return null;
}
/**
* 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;
}
/**
* @ejb.create-method
* @ejb.transaction type="Required"
*/
public void ejbCreate ()
throws javax.ejb.CreateException
{
}
public void ejbPostCreate ()
throws javax.ejb.CreateException
{
}
protected javax.ejb.SessionContext _ctx = null;
public void setSessionContext(javax.ejb.SessionContext ctx)
{
_ctx = ctx;
}
protected javax.ejb.SessionContext getSessionContext()
{
return _ctx;
}
}
|
请注意,PhoneBookSessionBean.java 实际上只定义了 JNDI 名称、方法的事务属性、home 接口的名称、remote 接口以及会话 EJB 的其他东西。在会话 EJB 的 J2EE 部署符中通常会看到的其他东西,是用会话 EJB 的 Java 源代码中定义的标注生成的。这包括每个方法的事务类型,甚至 EJB 是无状态 EJB 这一事实。
XDoclet 还生成一个工具类,用来查询 home 和 remote 接口。为大量不同的应用程序编写 EJB 的开发人员会认识到这一点的好处。这个查询代码很麻烦,而且说实话,编写起来很烦人。开发人员总是在以前做过的工作中寻找可利用的代码,或者搜索 Web,并把这个代码复制、粘贴到自己正在开发的新应用程序中。现在 XDoclet 替他们做了这件事。
最后的步骤
在编写完 EJB 并像前面描述的那样生成了部署描述符之后,在运行 Maven 构建代码之前,还有一些事要做。清单 6 显示的 application.xml 是 Apache Geronimo 要求的部署描述符。这只是一个标准的 J2EE 1.4 要求。它定义了 Java 档案(JAR)文件、WAR 文件和作为这个 J2EE EAR 的组成部分所需要的其他外部资源。
清单 6. Application.xml
<application
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/application_1_4.xsd"
version="1.4">
<module>
<ejb>phonebook-ejb.jar</ejb>
</module>
<module>
<web>
<web-uri>phonebook.war</web-uri>
<context-root>/phonebook</context-root>
</web>
</module>
<module>
<connector>
tranql-connector-1.0-SNAPSHOT.rar</connector>
</module>
</application>
|
在所有这些 XML 文件就位之后,可以运行示例提供的 Maven 构建脚本,迅速构建全部源代码。这个过程包括使用 XDoclet 库生成代码。Maven 设置起来有点儿麻烦。请按照 “Dive into EJB Web applications with Geronimo” 这篇文章中的步骤设置并运行它。
结束语
这篇文章解释了如何生成使用 Apache Geronimo 部署 J2EE 应用程序所需要的不同的部署描述符。通过展示在运行 Apache Geronimo 的计算机上编写和部署 EJB 有多么简单,演示了 XDoclet 和 Maven 的威力。
这篇文章有力地证明:在运行 Apache Geronimo 的计算机上部署 EJB 应用程序的步骤实际上与其他符合 J2EE 规范的应用服务器(例如 JBoss、WebSphere 或 WebLogic)没有什么不同。即使使用其他应用服务器,也可以使用 Maven 和 XDoclet。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| Source code for example | GeronimoPhonebook2.zip | 128 KB | HTTP |
|---|
参考资料 学习
获得产品和技术
讨论
关于作者  | 
|  | Kunal Mittal 是一位专攻 Java 技术、J2EE 和 Web 服务技术的顾问。他是这些主题方面的多部著作的合作者或者做出过贡献。他目前在为 Sony Pictures Entertainment 的一个门户项目工作。要了解更多信息,请访问他的 Web 站点 http://www.soaconsultant.com。 |
对本文的评价
|