Apache Geronimo JNDI 命名和 Java 资源连接池,第 2 部分: 使用 Java Message Service

使用 Geronimo 控制台创建和访问 JMS 资源组

Java™ Naming and Directory Interface(JNDI)是一种应用程序编程接口(API)或库,它为应用程序提供了将名称与对象关联起来,以及根据对象的名称在目录中查找对象的方法。本文是系列教程的第二部分,展示了 Apache Geronimo、JNDI 与 Java Message Service(JMS)资源组如何相互关联。而且您将了解如何构建 JMS 资源连接,以及如何在使用 JNDI 的简单 Geronimo 应用程序中访问 JMS 资源连接。

Dale de los Reyes (dreyes4@hotmail.com), 自由撰稿人, 自由职业

Dale de los Reyes 于 1996 年毕业于加利福尼亚州立理工大学 San Luis Obispo 分校,在那里取得计算机科学学位。他拥有 J2EE、C++ for Microsoft® Windows® 和 COBOL for Mainframes 方面的应用程序开发经验。他的业余爱好包括摄影、练习武术和从事独立项目。



2007 年 6 月 07 日

简介

JMS 是允许软件组件创建、发送、接收和读取消息的 API。这些消息不需要消耗人力。当然,它们是各种软件应用程序之间通信的方式。通过消息进行交互的应用程序具有松散耦合的优点,因为只要双方使用相同的消息格式,那么一个系统中的更改就不会对另一个系统产生影响。JMS API 是通过第三方实现的,并且实现提供了异步通信和可靠的消息传送等优点。通信是异步的,因为组件或客户机可以发送或接收消息而无需等待接收方的认可。通信还是可靠的,因为使用 JMS 时系统保证消息被传送一次而且仅传送一次。要接收消息,应用程序需要一个 JMS 侦听程序,该侦听程序用于在指定目标侦听消息。

会话 bean 和实体 bean 可以发送 JMS 消息,但只能同步接收这些 JMS 消息。这样有可能绑定服务器端资源,因为组件必须等待消息被收到。在 Java 2 Platform, Enterprise Edition(J2EE)1.3 中,一种新的 Enterprise JavaBean(EJB) —— MDB 被引入了。这种 EJB 就像是 JMS 侦听程序,允许 Java 应用程序异步地接收消息。

本文中介绍的 Customer Service 实用程序是一个简单的 Web 应用程序,它允许用户将基本的客户信息保存到数据库中。我们将使用 Apache Ant 1.6.5 和 Java 1.4.2_10 构建该实用程序,将其部署到配有 Tomcat 的 Geronimo 1.1 上。还将同时使用 Apache Derby 数据库和 ActiveMQ JMS 提供程序,因为它们是随 Geronimo 附带的。

本部分是 本系列教程 的第 2 部分,继续介绍 第 1 部分 中开始讨论的 Customer Service 实用程序。这一次,所有客户感兴趣的内容都将对照一张已知主题列表来解析和比较。这些主题将被划分为特殊兴趣组。如果给定客户的兴趣匹配一个已知主题,则将该组以及客户的电子邮件地址保存到数据库中,以便将来提供更好的服务。要使用 JMS 实现此功能,系统将在发现组后发送消息。MDB 用于接收这些消息,进而调用实体 bean 把内容存储到数据库中。下一节将介绍如何使用 Geronimo 控制台创建 JMS 资源组。


设置 Apache Geronimo

Geronimo 控制台可以以一种用户友好的方式管理 Java 组件。当 Geronimo 启动后,可以访问位于 http://localhost:8080/console 的 Geronimo 控制台。默认的用户名和密码分别是 systemmanager

需要创建一张新数据库表,在存储客户可能感兴趣的特殊兴趣组时它是必需的:

  1. 单击左侧 Console Navigation 面板底部的 DB Manager 链接以显示 Derby 数据库管理器。
  2. 在 Use DB 下拉式菜单中选择 CustomerServiceDatabase 创建数据库表。
  3. 然后将清单 1 的内容剪切并粘贴到 SQL Command/s 文本框中,然后单击 Run SQL 按钮。
清单 1. SQL 脚本 CustomerService-Part2.sql
create table interestgroup (
   clientid integer primary key,
   groupname varchar(20),
   emailaddress varchar(30)
);

客户服务数据库现在应当会有两张表:customersinterestgroup

接下来需要创建 JMS 资源组:

  1. 单击左侧 Console Navigation 面板中的 JMS Resources 链接。
  2. 单击面板底部的 For ActiveMQ 链接以创建新的 JMS 资源组。
  3. 在 Configure Server Connection 页面的 Resource Group Name 字段中输入 CustomerServiceConnectionGroup。ActiveMQ 将用于在部署了示例应用程序的同一台 Geronimo 服务器中发送和接收消息。因而,ServerUrl 字段可以保留原样 —— 指向 localhost。
  4. 让其余字段保留原样,然后单击 Next。下一个页面 Current Progress 将指示当前进度。此时应当不会显示任何内容。
  5. 单击 Add Connection Factory 按钮。
  6. 在 Select Connection Factory Type 页面中,从 JMS Factory Type 下拉式菜单中选择 javax.jms.ConnectionFactory,然后单击 Next
  7. 在 Configure Connection Factory 页面的 Connection Factory Name 字段中输入 CustomerServiceConnectionFactory
  8. 让其余字段保留原样,然后单击 Next
  9. 下一个页面 Current Progress 现在应当会显示一张表,表示最近创建的连接工厂。单击 Add Destination 按钮。
  10. 在 Select Destination Type 页面中,从 JMS Destination Type 下拉式菜单中选择 javax.jms.Topic,然后单击 Next
  11. 在 Configure Destination 页面的 Message Destination Name 和 Physical Name 字段中,输入 CustomerServiceTopic,然后单击 Next
  12. 下一个页面 Current Progress 现在应当会显示一张更新的表,表示最近创建的主题。单击 Deploy Now 按钮。现在应当会显示类似图 1 的页面。
图 1. 新创建的 JMS 资源 CustomerServiceConnectionGroup
新创建的 JMS 资源 CustomerServiceConnectionGroup

CustomerServiceConnectionGroup 现在应当被部署并显示到 JMS Resources 面板中。CustomerServiceConnectionFactoryCustomerServiceTopic 也显示在此处并可供使用。下一节将详细说明如何在 JNDI 中声明这些对象。


创建 Geronimo 部署描述符

对描述符文件进行配置是十分重要的,因为它是使这些组件可用于 Geronimo 的机制。另外,还决定了 JNDI 名称应该如何与给定的 Java 对象关联起来。部署到 Geronimo 中的组件通常有两个部署文件:标准的 Java EE 部署描述符和特定于 Geronimo 的部署计划。

如上所述,当找到匹配客户兴趣的组后,系统将把一条消息发送给 JMS 目标。这条消息是从 ProcessCustomerSessionBean 中发送的。清单 2 包含在标准的 Java 部署描述符中指定 JMS 资源所需的附加标记。

清单 2. ProcessCustomerEJB-ejb.xml 的部分清单
<ejb-jar>
   <enterprise-beans>
      <session>
         <resource-ref>
            <description>JMS Broker</description>
            
<res-ref-name>jms/CustomerServiceConnectionFactory</res-ref-name>
            <res-type>javax.jms.ConnectionFactory</res-type>
            <res-auth>Container</res-auth>
         </resource-ref>

         <resource-env-ref>
            
<resource-env-ref-name>jms/CustomerServiceTopic</resource-env-ref-name>
            <resource-env-ref-type>javax.jms.Topic</resource-env-ref-type>
         </resource-env-ref>       
      </session>
   </enterprise-beans>
</ejb-jar>

两个附加标记是惟一值得关注的条目。<resource-ref> 标记用于为 JMS 连接工厂指定 JNDI 名称。连接工厂负责创建指向 JMS 提供程序(本例中为 ActiveMQ)的连接。<resource-env-ref> 标记用于为 JMS 主题指定 JNDI 名称。JMS 主题是发送和接收消息的目标。清单 3 显示了此会话 bean 的特定于 Geronimo 的关联文件。

清单 3. ProcessCustomerEJB-openejb.xml 的部分清单
<openejb-jar xmlns="http://www.openejb.org/xml/ns/openejb-jar-2.1"
        xmlns:naming="http://geronimo.apache.org/xml/ns/naming-1.1"
        xmlns:security="http://geronimo.apache.org/xml/ns/security-1.1"
        xmlns:dep="http://geronimo.apache.org/xml/ns/deployment-1.1">
   <dep:environment>
      <dep:moduleId>
         <dep:groupId>default</dep:groupId>
         <dep:artifactId>ProcessCustomerSessionBean</dep:artifactId>
         <dep:version>1.0</dep:version>
         <dep:type>jar</dep:type>
      </dep:moduleId>

      <dep:dependencies/>
      <dep:hidden-classes/>
      <dep:non-overridable-classes/>
   </dep:environment>

   <enterprise-beans>
      <session>
         <resource-ref>
            <ref-name>jms/CustomerServiceConnectionFactory</ref-name>
            <resource-link>CustomerServiceConnectionFactory</resource-link>
         </resource-ref>

         <resource-env-ref>
            <ref-name>jms/CustomerServiceTopic</ref-name>
            
<message-destination-link>CustomerServiceTopic</message-destination-link>
         </resource-env-ref>
      </session>
   </enterprise-beans>
</openejb-jar>

两个附加标记同样是值得关注的内容。<resource-ref> 将把 JNDI 名称与上一节在 Geronimo 控制台中创建的 CustomerServiceConnectionFactory 关联起来。类似地,<resource-env-ref> 标记将把 JNDI 名称与 JMS 主题 CustomerServiceTopic 关联起来。这就是使 JMS 资源在 JNDI 中可用于会话 bean 所需的全部操作。清单 4 显示了用于 MDB ServiceMonitorMessageBean 的标准描述符。

清单 4. ServiceMonitorEJB-ejb.xml 的部分代码
<ejb-jar xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee        	
version="2.1">
   <enterprise-beans>
      <message-driven>
         <ejb-name>ServiceMonitorMessageBean</ejb-name>
         
<ejb-class>com.service.customer.ejb.ServiceMonitorMessageBean</ejb-class>
         <messaging-type>javax.jms.MessageListener</messaging-type>
         <transaction-type>Container</transaction-type>

         <activation-config>
            <activation-config-property>
            
<activation-config-property-name>destination</activation-config-property-name
>
            
<activation-config-property-value>CustomerServiceTopic</activation-config-
property-value>
            </activation-config-property>

            <activation-config-property>
            
<activation-config-property-name>destinationType</activation-config-property-
name>
            
<activation-config-property-value>javax.jms.Topic</activation-config-
property-value>
            </activation-config-property>
         </activation-config>

         <ejb-ref>
            <ejb-ref-name>ejb/InterestGroupEntityBean</ejb-ref-name>
            <ejb-ref-type>Entity</ejb-ref-type>
            <home>com.service.customer.ejb.InterestGroupHome</home>
            <remote>com.service.customer.ejb.InterestGroup</remote>
         </ejb-ref>
      </message-driven>
   </enterprise-beans>
</ejb-jar>

此处有若干个值得关注的标记。首先,<ejb-name> 标记用于指定 MDB 的名称。<ejb-class> 标记用于指示实现类的全限定名称。不同于会话或实体 bean,MDB 不需要使用 home/localhome 或 remote/local 接口。仅需要使用 bean 实现。<messaging-type> 标记用于指定 JMS 消息侦听程序的类。

<activation-config> 标记用于为消息被发送到的 JMS 目标命名。它还用于指定此目标需要的消息域类型。通常,这是一个主题或队列,用于分别表示两个消息模型:发布/订阅或点对点。最后,<ejb-ref> 标记用于声明在 MDB 内使用的 EJB 的 JNDI 名称。清单 5 显示了对应的特定于 Geronimo 的计划。

清单 5. ServiceMonitorEJB-openejb.xml 的部分清单
<openejb-jar xmlns="http://www.openejb.org/xml/ns/openejb-jar-2.1"
        xmlns:dep="http://geronimo.apache.org/xml/ns/deployment-1.1">
   <dep:environment>
      <dep:moduleId>
         <dep:groupId>default</dep:groupId>
         <dep:artifactId>ServiceMonitorEJB</dep:artifactId>
         <dep:version>1.0</dep:version>
         <dep:type>jar</dep:type>
      </dep:moduleId>

      <dep:dependencies>
         <dep:dependency>
            <dep:groupId>console.jms</dep:groupId>
            <dep:artifactId>CustomerServiceConnectionGroup</dep:artifactId>
            <dep:version>1.0</dep:version>
            <dep:type>rar</dep:type>
         </dep:dependency>
      </dep:dependencies>

      <dep:hidden-classes/>
      <dep:non-overridable-classes/>
   </dep:environment>

   <enterprise-beans>
      <message-driven>
         <ejb-name>ServiceMonitorMessageBean</ejb-name>

         <resource-adapter>
            <resource-link>CustomerServiceConnectionGroup</resource-link>
         </resource-adapter>

         <ejb-ref>
            <ref-name>ejb/InterestGroupEntityBean</ref-name>
            <ejb-link>InterestGroupEntityBean</ejb-link>
         </ejb-ref>
      </message-driven>
   </enterprise-beans>
</openejb-jar>

<dep:moduleId> 用于指定此组件的全名。<dep:dependencies> 标记用于指示对 JMS 资源 CustomerServiceConnectionGroup 的依赖性。可以在 geronimo-1.1/repository 中找到此组件。同会话或实体 bean 一样,<ejb-name> 标记必须与先前列出的标准描述符中的 <ejb-name> 标记相对应。<resource-adapter> 标记用于为要使用的资源(本例中为 JMS 资源)命名。最后,<ejb-ref> 标记用于把 JNDI 名称与 EJB 的名称关联起来。这就是声明 JNDI 名称并将其与 JMS 资源组关联起来所需的操作。接下来,将了解如何在代码中查找这些对象。


Customer Service 实用程序和 JMS

增强的 Customer Service 实用程序几乎已经就绪。接下来,需要使用 JNDI 调用 JMS 资源才能使用 JMS。清单 6 中显示了命名约定以及完整的 JNDI 字符串。

清单 6. customer.properties 中的 JNDI 名称列表
# Specify JNDI names here
jndi.customer.ejb=java:/comp/env/ejb/CustomerEntityBean
jndi.process.ejb=java:/comp/env/ejb/ProcessCustomerSessionBean
jndi.group.ejb=java:/comp/env/ejb/InterestGroupEntityBean
jndi.jms.connector=java:comp/env/jms/CustomerServiceConnectionFactory
jndi.jms.topic=java:comp/env/jms/CustomerServiceTopic

# Specify Special Interest Groups here
all.groups=java,sports,travel

# Specify key words for each Special Interest Group
# Note: group names must end with .group
java.group=java,graphics,geronimo,j2ee,wireless,eclipse,tech,research,test,auto
sports.group=sport,biking,hiking,ball,tennis,golf,martial arts,tae kwon do,yoga
travel.group=travel,new zealand,europe,japan,hawaii,vacation,cruise,photo

先前在会话 bean 描述符的 <ref-name> 标记中声明的名称现在按照其完整的 JNDI 名称列出。所有名称都带有符合建议的 Java 命名约定的前缀。此外请注意,特殊兴趣组及其相关主题列于此文件中。清单 7 中的代码显示了如何在使用完整 JNDI 名称的 MDB 中执行 InterestGroupEntityBean JNDI 查找。

清单 7. ServiceMonitorMessageBean.java 的部分清单
package com.service.customer.ejb;

public class ServiceMonitorMessageBean implements MessageDrivenBean, MessageListener
{
   private transient MessageDrivenContext mdc = null;
   private InterestGroupHome groupHome = null;
   private ResourceBundle    bundle = null;
   private String            JNDI_GROUP_EJB = null;

   public void ejbCreate()
   {
      InitialContext initial = null;
      Object objref = null;

      bundle = ResourceBundle.getBundle("customer", Locale.getDefault(), 
ServiceMonitorMessageBean.class.getClassLoader());
      JNDI_GROUP_EJB = bundle.getString("jndi.group.ejb");

      try
      {
         initial = new InitialContext();
         objref = initial.lookup(JNDI_GROUP_EJB);
         groupHome = (InterestGroupHome)PortableRemoteObject.narrow(objref, 
InterestGroupHome.class);
      } // end try

      catch (Exception e)
      {
         e.printStackTrace();
      } // end catch
   } // end ejbCreate

   public void onMessage(Message msg)
   {
      boolean entryFound = false;
      GroupKey groupKey = null;
      InterestGroup group = null;
      ObjectMessage objMsg = null;
      Vector tmpVector = null;

      // Extract message from Topic
      try
      {
         if (msg instanceof ObjectMessage)
         {
            objMsg = (ObjectMessage)msg;
            groupKey = (GroupKey)objMsg.getObject();
            System.out.println("JMS - id: " + groupKey.clientID + "\t" +
                               "group: " + groupKey.groupName + "\t" +
                               "email: " + groupKey.emailAddress);
         } // end if
      } // end try

      catch (Exception e)
      {
         e.printStackTrace();
      } // end catch

      // Create "group" if no entry found
      if (!entryFound)
      {
         try
         {
            group = groupHome.create(groupKey);
         } // end try

         catch (Exception e)
         {
            e.printStackTrace();
         } // end catch
      } // end if
   } // end onMessage
} // end ServiceMonitorMessageBean

值得关注的两个方法是 ejbCreateonMessageejbCreate 方法用于执行 InterestGroupEntityBean 的 JNDI 查找。这段查找代码类似于会话或实体 bean 中的代码。只要在 CustomerServiceTopic 中发现消息,就将调用 onMessage 方法。这种方式是组件访问 MDB 的惟一方法,因为这些 bean 都不是被直接调用的。清单 8 显示了如何在 ProcessCustomerSessionBean 会话 bean 内查找 JMS 资源。

清单 8. ProcessCustomerSessionBean.java 的 doJNDILookups 方法
private void doJNDILookups()
      throws NamingException, ClassCastException
   {
      Object objref = null;
      InitialContext initial = null;

      bundle = ResourceBundle.getBundle("customer", Locale.getDefault(), 
ProcessCustomerSessionBean.class.getClassLoader());
      JNDI_CUSTOMER_EJB = bundle.getString("jndi.customer.ejb");
      JNDI_JMS_CONNECTOR = bundle.getString("jndi.jms.connector");
      JNDI_JMS_TOPIC = bundle.getString("jndi.jms.topic");

      initial = new InitialContext();
      objref = initial.lookup(JNDI_CUSTOMER_EJB);
      customerHome = (CustomerHome)PortableRemoteObject.narrow(objref, 
CustomerHome.class);
      System.out.println("looking up: " + JNDI_CUSTOMER_EJB);

      objref = initial.lookup(JNDI_JMS_TOPIC);
      receivingTopic = (Topic)PortableRemoteObject.narrow(objref, Topic.class);
      System.out.println("looking up: " + JNDI_JMS_TOPIC);

      objref = initial.lookup(JNDI_JMS_CONNECTOR);
      factory = (ConnectionFactory)PortableRemoteObject.narrow(objref, 
ConnectionFactory.class);
      System.out.println("looking up: " + JNDI_JMS_CONNECTOR);
   } // end doJNDILookups

工厂和 receivingTopic 都是从 JNDI 获得的,并分别与资源 CustomerServiceConnectionFactoryCustomerServiceTopic 相对应。这两种资源都是使用 Geronimo 控制台创建的。清单 9 显示了如何使用这些 JMS 资源将消息发送给目标。

清单 9. ProcessCustomerSessionBean.java 的 sendMessage 方法
private void sendMessage(String groupName, String emailAddress)
   {
      boolean transacted = false;
      Connection connection = null;
      GroupKey groupKey = null;
      MessageProducer producer = null;
      ObjectMessage objMsg = null;
      Session session = null;

      groupKey = new GroupKey();
      groupKey.clientID = generateSimpleID();
      groupKey.groupName = groupName;
      groupKey.emailAddress = emailAddress;

      try
      {
         connection = factory.createConnection();
         session = connection.createSession(transacted, Session.AUTO_ACKNOWLEDGE);
         connection.start();

         producer = session.createProducer(receivingTopic);
         objMsg = session.createObjectMessage();
         objMsg.setObject(groupKey);
         producer.send(objMsg);

         connection.close();
      } // end try

      catch (Exception e)
      {
         e.printStackTrace();
      } // catch
   } // end sendMessage

sendMessage 方法将通过工厂创建一个指向 ActiveMQ JMS 提供程序的 JMS 连接。然后再将 receivingTopic 指定为发送消息期间的消息目标。消息被发送后,会话就会被关闭。

Customer Service 实用程序现在就准备好可以部署到 Geronimo 上了:

  1. 修改 CustomerService/resources/build.properties 文件,并确保正确定义了目标位置。
  2. 接下来,打开控制台,将目录切换到 CustomerService/build.xml 的位置,然后键入 ant。此操作将构建并部署应用程序,可在 http://localhost:8080/service 访问该应用程序。

图 2 显示了数据条目页面的外观。

图 2. Customer Service 实用程序的数据条目页面
Customer Service 实用程序的数据条目页面

只有最基本的客户信息被存储到数据库中。客户的兴趣被从会话 bean 中解析出来,并与已知主题列表相比较以确定组名。图 3 显示了所有客户信息经过处理或被输入后的结果表。

图 3. interestgroup 表的数据库视图
interestgroup 表的数据库视图

在将每个客户的兴趣与 customer.properties 文件中的主题列表比较之后,很明显客户兴趣有些重叠之处。这是正常的,并且在将特殊兴趣组指定给客户后重复条目会被忽略。这就是在 JNDI 中访问 JMS 资源所需的全部操作!

结束语

本文向您介绍了如何使用 Geronimo 控制台创建 JMS 资源组。Customer Service 实用程序演示了如何使用会话 bean 将消息发送到 JMS 主题,并使用 MDB 同步接收那些消息,从而从 JNDI 访问此资源。请继续关注第 3 部分,将讨论 JNDI 如何连接至邮件会话资源以及如何与示例应用程序集成。


下载

描述名字大小
第 2 部分的源代码CustomerService-part2.zip226KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source, Java technology, WebSphere
ArticleID=261459
ArticleTitle=Apache Geronimo JNDI 命名和 Java 资源连接池,第 2 部分: 使用 Java Message Service
publish-date=06072007