IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Open source | Java technology  >

在 Apache Geronimo 上部署 J2EE 应用程序

用 OpenEJB 和 XDoclet 处理部署描述符

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

讨论

样例代码


级别: 中级

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
  • 无状态会话 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 exampleGeronimoPhonebook2.zip128 KBHTTP
关于下载方法的信息


参考资料

学习

获得产品和技术

讨论


关于作者

Kunal Mittal 是一位专攻 Java 技术、J2EE 和 Web 服务技术的顾问。他是这些主题方面的多部著作的合作者或者做出过贡献。他目前在为 Sony Pictures Entertainment 的一个门户项目工作。要了解更多信息,请访问他的 Web 站点 http://www.soaconsultant.com




对本文的评价










回页首


IBM 和 WebSphere 是 IBM 公司在美国和其他国家的注册商标。 其他公司、产品或服务的名称可能是其他公司的商标或服务标志。

IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款