使用 WebSphere Studio Application Developer 集成版,版本 4.1 开发 Java 消息服务应用程序

介绍
分布式应用程序可以使用面向消息的中间件(MOM)在企业应用程序的组件之间异步发送和接收消息。由于消息传递的异步性,MOM 系统提供了很大的灵活性,它们不遵照几乎所有其它通信接口都使用的请求?响应模式。

MOM 系统让发送方,或称消息生产者(producer),将消息发送到消息传递中间件,然后忘掉它。MOM 系统保证消息传递。接收方,或称消息消费者(consumer),不控制消息传递的时间或顺序。消息消费者不必轮询或等待过来的消息,当消息到达时会通知消费者。这篇文章的大部分内容是讨论设计一个消费者,使它可以一直处理异步到达的消息。

Java™ 2 平台企业版(J2EE)为 Java 程序与 MOM 系统相互连接定义了一个标准 API 和体系结构 ? Java 消息服务(Java Message Service)。J2EE 版本 1.3 引入了一类新的 Enterprise JavaBean™(EJB)? 消息驱动 Bean(Message Driven Bean,MDB),它是为接受异步传入的消息而特别设计的。开发新的和转换现有的使用消息传递的应用程序的战略方法是采用这些 J2EE 标准。

本文讨论了如何使用 WebSphere® 产品的当前版本来开发、测试和部署 JMS 应用程序。本文无法涵盖这个广阔领域的所有方面,所以重点讨论异步消费消息以及配置测试环境的解决方案。

您可以使用 WebSphere 产品系列中的这些产品来开发和运行使用消息传递的应用程序。

  • WebSphere MQ,版本 5.2.1是 IBM 消息传递软件。WebSphere MQ 以前被称为 MQSeries®,它有很大的用户群。许多应用程序正在从专有的 WebSphere MQ API 移到 JMS。
  • WebSphere Application Server,版本 4.0.2在高级版(AE)和高级单服务器版(AEs)中都支持 JMS API。使用 Enterprise Extensions v4.0(EEX)的 Extended Messaging Support(EMS),可以将接受异步到达的消息的任务委托给应用程序服务器。EEX 可以添加到 AE 和 AEs。Application Server 版本 5 将支持消息驱动 Bean(MDB)。
  • WebSphere Studio Application Developer,版本 4.0.2提供了一个开发和测试 Java 应用程序的环境。Application Developer 中包含的 WebSphere 测试环境是 AEs 版本 4.0.2。
  • WebSphere Studio Application Developer,集成版,版本 4.1支持 EMS。Application Developer IE 中包含的 WebSphere 测试环境是 AEs 版本 4.0.2 加一些 EEX 功能部件。

JMS API
JMS 定义了两种消息传递方式:点对点(P2P)和发布/订阅(pub/sub)。在点对点方式中,消息生产者通过一个队列将消息发送到一个特定的消息消费者。在 P2P 生产者和消费者之间有一个一对一的关系。点对点方式最直接对应于消息传递的传统使用,因此它是现有 WebSphere MQ 应用程序更可能的迁移路径。本文中的所有样本都使用 P2P 消息传递。

对于传统的 WebSphere MQ 用户来说,发布/订阅比较陌生,同时也提供了很大的潜能。在 pub/sub 方式下,任意数目的消息生产者都可以将消息发送到主题而非队列。任意数目的消费者都可以订阅主题并接收消息。所以说 pub/sub 考虑到了广播消息以及生产者与消费者之间的多对多关系。本文关于创建侦听器的技术讨论同样适用于 pub/sub 方式中的主题订户。

关于 JMS API 的比较好的介绍,请参阅下列教程之一:

J2EE 要求应用程序通过映射到对象和类的 JNDI 名称空间内的逻辑名来引用 JMS 目标(队列或主题)和连接工厂(connection factory)。WebSphere MQ Java 支持包含 JMSAdmin实用程序 ? 一个用于定义受 WebSphere MQ 管理的对象(如队列和主题),并把它们绑定到 JNDI 名称空间的 Java 应用程序。JNDI 上下文可以由应用程序服务器(比如 WebSphere Application Server)、LDAP 服务器或者本机文件系统来提供。

安装和验证
在运行样本程序之前,您应该测试 WebSphere MQ 的安装并从 Application Developer IE 连接到它。请在Windows® 2000 service pack 1 或者 Windows NT® service pack 6a 或更高版本上安装下列产品。

  • WebSphere MQ,版本 5.1 或更高版本。对于 pub/sub,您需要 WebSphere MQ 5.2 或更高版本。
  • WebSphere MQ 产品扩展 MA88。pub/sub 还需要产品扩展 MAOC,但本文中的样本不用这个产品扩展。
  • Application Developer IE 版本 4.1。注意:本文中的安装验证程序和第一个 JMS 样本可以在 Application Developer 版本 4.0.2 或更高版本上运行。

WebSphere MQ 安装提示:

  • 请务必设置一个缺省配置。本文中的样本使用缺省的队列管理器和一些缺省的系统队列。
  • 为 WebSphere MQ 和 MA88 选择定制安装,这样您就可以指定安装目录。否则,您可能会发现 WebSphere MQ 安装到了 C:/Program Files/MQSeries ,而 MA88 安装到了 C:/Program Files/IBM/MQSeries/Java
  • 确保 ../MQSeries/Java/lib 目录在您的 PATH 环境变量上,这样系统就可以发现 DLL 是在那个位置上。

安装过 Application Developer IE 后,设置一个全局 Java 类路径变量。

  1. 在主工具栏上选择 Windows => Preferences。然后,展开 Java并选择 Preferences 窗口左边的 Classpath Variables。单击右边的 New
  2. 将新变量命名为 MQ_JAVA_INSTALL_PATH ,并将它的值设为安装 MA88 的绝对路径。通常该路径以 .. /MQSeries/Java 结尾。
  3. 单击 OK两次,保存该变量。

本文中的进一步指示假设您熟悉如何使用 Application Developer。

运行 IVT 验证程序
随 MA88 一起提供了许多测试程序。 Using Java Messaging Service with WebSphere and VisualAge® for Java一文描述了如何运行它们。带有 JNDI 查找的 P2P 测试 MQJMSIVT ,已经足可以为本文中的样本准备,下面说明了运行它的快速方法。

如果一个程序使用尚未绑定到 JNDI 名称空间的受管对象,在运行这个程序之前,请运行 JMSAdmin 。通常,运行 JMSAdmin 的指导描述在命令行窗口从批处理文件启动它。从内部 Application Developer IE 运行 JMSAdmin 简化了 Java 环境的设置,特别是类路径的设置。

包含下列类的 JAR 文件包含在 下面的下载部分中。


描述
IVTRunNoJndi不使用 JNDI 运行 MQJMSIVT 测试程序。
IVTRunFSJndi运行 MQJMSIVT 测试程序,使用文件系统 JNDI 查找所管理的对象。
IVTRunWASJndi运行 MQJMSIVT 测试程序,使用 WebSphere 测试环境 JNDI 名称空间查找所管理的对象。
JMSAdminFS使用配置文件 JMSAdminFS.config 运行 JMSAdmin 实用程序,该配置文件指定文件系统 JNDI 上下文。
JMSAdminWAS使用配置文件 JMSAdminWAS.config 运行 JMSAdmin 实用程序,该配置文件指定 WebSphere JNDI 上下文。

这些类和一些附加的文件包含在 verification 包中。如果您看一下这些类的代码,您会看到它们调用反射 API 来加载 MQJMSIVT 类,指定命令行参数,然后运行 main 方法。

这些指令使用文件系统 JNDI 上下文,这意味着绑定信息被写到 JMSAdmin 配置文件中指定的 Windows 目录。因此,您无需启动 WebSphere 测试环境来验证 WebSphere MQ 到 JMS API 的连接。

  1. 下载并解压缩 下面的样本文件。在解压缩时请确保保留了文件夹名。
  2. 如果不存在目录 D:/temp ,则创建它。所提供的 JMSAdminFS.config 文件将 D:/temp 设置为存储 JNDI 绑定的目录。
  3. 打开 Application Developer IE 并转到 Java 透视图。创建一个 Java工程来保存这个样本中的类。您可以称此工程为 JMSsamples
  4. 为您的工程设置 Java 构建路径。

    1. 从工程的 Properties 表单(sheet)选择 Java Build Path,单击 Libraries选项卡,然后选择 Add Variable
    2. 使用已定义的类路径变量添加:
      
      MQ_JAVA_INSTALL_PATH/lib/com.ibm.mq.jar
      MQ_JAVA_INSTALL_PATH/lib/com.ibm.mqjms.jar
      MQ_JAVA_INSTALL_PATH/lib/fscontext.jar
      MQ_JAVA_INSTALL_PATH/lib/providerutil.jar
      WAS_PLUGINDIR/lib/j2ee.jar
      WAS_PLUGINDIR/lib/websphere.jar
                  
  5. 将文件 verification.jar 导入到工程,从而将 verification 包添加到工程中。如果有类发生了错误,请检查您的构建路径。
  6. 将类 JMSAdminFS 作为一个 Java 应用程序来运行。在 Console 中,查看提示符 InitCtx>
  7. 输入 JMSAdmin 命令。JMSAdmin 命令和关键字不区分大小写,但名称(包在圆括号中)区分大小写。
    
    define qcf(ivtQCF)
    define q(ivtQ) queue(SYSTEM.DEFAULT.LOCAL.QUEUE)
    display ctx
                

    检查对象 ivtQCFivtQ 是否列在显示的 JNDI 上下文中

    
    end
                

    关键字 qcf 定义了一个 com.ibm.mq.jms.MQQueueConnectionFactory 对象。关键字 queue (简称 q )定义了一个 com.ibm.mq.jms.MQQueue 对象。请参阅 WebSphere MQ 产品信息中心( Start => Programs => IBM MQSeries => Information Center)以获取 JMSAdmin 关键字和命令的完整列表。

  8. IVTRunFSJndi 作为一个 Java 应用程序运行。如果出现任何异常或与以下内容不同的输出,都说明您的安装或设置有问题。

5648-C60 (c) Copyright IBM Corp. 1999. All Rights Reserved.
MQSeries classes for Java(tm) Message Service 5.200
Installation Verification Test
... lines omitted
Sending the message to SYSTEM.DEFAULT.LOCAL.QUEUE 
Reading the message back again
... lines omitted
Reply string equals original string
... lines omitted
IVT completed OK
IVT finished
    

样本案例
本文中的示例都是只基于一个案例,在实现异步接收消息的不同设计模式的各个阶段被编码。第一阶段演示了 JMS API。第二阶段使用 Application Server 和 WebSphere 测试环境,第三阶段使用 Application Developer IE 的 EMS 功能部件。最后阶段考虑到了向消息驱动 Bean 的转换。

通常情况下,这个案例如下图 1 所示。企业应用程序被称为 Count,Count 客户机通过排队的消息与之进行通信。消息的主体是文本。企业任务是统计出消息中的字符数。于是 Count 应用程序将响应消息放入不同的队列。这是一个从客户端发出请求的请求-响应案例。这个简单的客户机发送一条请求消息,然后在应答队列中等待,直到响应消息变为可用或者接收操作超时。相反,企业端应该是一直准备着消费来自客户机的消息,并且它的行动是从异步接收开始。

图 1. 一般案例
一般案例图解

在 Count 客户机上:

  • 消息生产者向 Q1 发送消息。
  • 消息消费者接收到来自 Q2 的应答。

在 Count 应用程序上:

  • 一个侦听器接收到来自 Q1 的异步消息。
  • 必须有某个东西来启动侦听器并让它保持运行状态
  • 消息消费者接收到来自侦听器的消息并将它发送给应用程序。
  • Count 逻辑执行企业任务。
  • 消息生产者将响应放在 Q2 上。

要运行该案例,请在 CountClient 前启动 CountApplication 类。否则,客户机超时,将响应消息留在队列中。您可以一直用手工方式从 MQ Explorer 清除队列。为便于演示,Count 应用程序消费一条消息,产生一个应答,然后停止。

第 1 阶段. 编写自己的侦听器
Count 客户机调用 QueueSender.sendQueueReceiver.receive 方法设置并获取来自队列的消息。更有趣的是 Count 应用程序,因为它必须由消息的到达来激活。API 包含一个 MessageListener 接口。第一阶段的解决方案是编写这个接口的实现,从而产生如下图 2 所示的设计。如果您有了一个侦听器,您的程序就由消息到达这个外部事件来驱动。当消息到达时,JVM 自动调用您的侦听器方法并向其传递消息,一次传递一条。

图 2. 编写一个侦听器
编写侦听器的图解

在 count 客户机上:

  • 消息生产者向 Q1 发送消息。
  • 消息消费者接收到来自 Q2 的应答。

在 Count 应用程序上:

  • JVM 将消息从 Q1 移到侦听器。
  • Count 的 main 方法启动侦听器。
  • 一个方法消费通过侦听器发送到它的消息并将这些消息交给逻辑方法。
  • 逻辑方法统计字符数。
  • 一个方法将响应发送到 Q2。

一个程序可能有几个侦听器。每个入站队列都要求一个单独的侦听器,一个队列的多个侦听器可能使用的是不同的消息选择器。每个侦听器都变成了进入应用程序的一个入口点。要编写一个侦听器:

  • 创建一个实现接口 javax.jms.MessageListener 的类。您必须以方法说明 public void onMessage(Messagemessage) 提供一个方法。
  • QueueReceiver 对象注册这个类 ? 方法是将其作为 createQueueReceiver 方法的一个参数进行传递。
  • 只要程序准备消费消息,就要设法让侦听器保持为活动状态。

实现 MessageListener 有多种选择,包括创建一个独立的类和在 createQueueReceiver 的参数中定义一个匿名的内部类。Count 应用程序是如此小且简单,以至于它的一个主类就可以实现消息侦听器,如下面这个代码段所示。


public class CountApplication implements MessageListener { 
        
        
public void onMessage(Message message) { // pass text messages only on to the application try { // the cast throws an exception is the message is not a Text Message consumeMessage((TextMessage) message); } catch (ClassCastException e) { System.out.println("Got message of type " + message.getClass()); e.printStackTrace(System.err); } } }

在 Count 应用程序中, consumeMessage 方法记录消息已收到,并将消息的主体传递给 doCountLogic 方法。方法 doCountLogic 确定字符串的长度,然后调用 produceResponseMessage 方法来生成响应。

这个解决方案最棘手的部分是管理侦听器的生命期。一个常用技巧是为程序中的每个侦听器启动一个线程。但您仍必须生成一些动作来保持侦听器线程处于活动状态。Count 应用程序在方法 startListener 中处理了这个问题,如下一段代码摘录所示。Count 应用程序只需一个线程,但它使用了多线程应用程序中常用的一种技术;它让线程一直保持在暂挂状态直到被外部事件调用。


public class CountApplication implements MessageListener { 
        
        
// define a condition to stop listening private boolean messageReceived = false; public static void main(String [] args) { // ... CountApplication app = new CountApplication(); app.startListener(); } void startListener() { try { // start the connection, create the session // ... qReceiver = qSession.createReceiver(requestQueue); qReceiver.setMessageListener(this); // loop to keep listener alive while (! messageReceived ) { try { // pause for a second to share the CPU Thread.currentThread().sleep(1000); } catch (InterruptedException ie) {} } } catch (JMSException e ) { e.printStackTrace(System.err); } }

运行第一个 count 样本
在运行这个样本之前,请在 WebSphere MQ 中定义几个队列。打开一个命令行窗口并发出下列命令来定义队列。如果您熟悉 MQ Explorer,您也可以在那儿创建队列。


WebSphere MQ 命令 描述
startmqsc 如果缺省队列管理器还没运行,请启动它。
runmqsc 启动一个命令序列(不期望有提示符)。
define qlocal(Q1)
   put(enabled)
get(enabled)
创建请求队列(输入在一行上)。
define qlocal(Q2)
   put(enabled)
get(enabled
创建响应队列(输入在一行上)。
display qlocal(Q*) 列出以 Q 开头的队列以供验证。
end 停止 runmqsc

下面的下载 ZIP 文件中的 JAR 文件提供 Count 样本这一阶段的源代码。

  1. 打开 Application Developer 并转到 Java 透视图。
  2. Stage1.jar 导入到 Java 工程。您可以将它添加到 JMSsamples 工程,然后会看到一个新包 samples.count.stage1
  3. 如果您要创建一个新工程,就象您运行验证程序时那样设置工程的构建路径。
  4. 象以前那样运行 JMSAdminFS 从而将更多的对象绑定到 JNDI 名称空间。在 InitCtx 提示符下,输入:
    
    define queue(JMSQ1) queue(Q1)
    define queue(JMSQ2) queue(Q2)
    define QCF(JMSQCF)
    display ctx
    end
                
  5. 将 CountApplication 类作为一个 Java 应用程序来运行。预期输出为:
    
    getting jndi context
    Retrieving JMS objects from JNDI
    Listening to: queue:///Q1
    Unable to load message catalog - mqji
                

    忽略了关于消息目录的虚假消息。

  6. 将 CountClient 类作为一个 Java 应用程序来运行。预期输出为:
    
    getting jndi context
    Retrieving JMS objects from JNDI
    sending message <Hello World!> to queue:///Q1
    Unable to load message catalog - mqji
    message sent
    Getting message from queue:///Q2
    Message <Hello World!: Contains 12 characters> received
                
  7. 现在,如果再看一下 Count 应用程序的输出,您会看到新的输出行:
    
    Message <Hello World!> received
    sending message <Hello World!: Contains 12 characters> to queue:///Q2
    message sent
                
  8. 通过以不同的次数运行客户机和应用程序,您可以在队列中建立一些未消费的消息。可以使用 MQ 浏览器(MQ Explorer)浏览队列中当前存在的消息,还可以清除队列。

这个样本中没有包括异常侦听器。当出现阻止消息传递的问题(一种 MessageListener 无法检测的情况)时,就会调用异常侦听器。要创建一个异常侦听器,请创建一个实现 javax.jms.ExceptionListener 的类,提供一个 onException 方法,并通过调用队列连接对象的 setExceptionListener 注册异常侦听器。

在这一阶段,您有一个可运行的 JMS 应用程序。客户端仍然遵守请求-应答用法模式。假设没有用户在等待应答出现在 UI 上,就可以重写客户机,将接收响应的任务委托给侦听器。这是一次意义重大的重新设计,但它使整个应用程序更加健壮,并且对定时更加不敏感。在发送 ? 忘记(send-and-forget)模式中,应用程序端首先是一个异步消费者和生产者。

WebSphere 测试环境没必要运行这个样本。但是,下一阶段是要考察应用程序服务器能否提供一个解决方案来解决管理侦听器生命期的问题。

第 2 阶段. 使用 WebSphere Application Server 运行侦听器
目前为止,Count 客户机和应用程序都是从它们自己的 main 方法开始运行的 Java 应用程序。更实际一些,应用程序应该运行在服务器上,或许还为用户提供 Web 接口。

这一阶段考虑重新设计 Count 应用程序以便能将其部署到 Application Server。合理的方法是将 JMS API 封装在消费消息和生成消息的 bean 中。业务逻辑可以留在 CountApplication 类中。这些 bean 是成为 EJB 还是成为简单的 Java bean 取决于应用程序的需要。要把一个侦听器包在一个 Application Server 支持的事务中,必须使用 EMS,如第 3 阶段所述。对于 Count 应用程序,Java bean 就足够了。在这个案例中,Count 逻辑没什么事好做,只是统计一下传递给 doCountLogic 方法的字符串中的字符数,并调用 JMS 生产者 bean 将响应放在应答队列中。

您安装在 Application Developer 上的所有应用程序都必须作为J2EE 企业应用程序部署,同时,EJB 模块部署到 EJB JAR 而 Web 模块部署到 WAR 文件。JMS 和 Count 逻辑 Javabean 可以包含在 Web 模块中。用什么来启动侦听器呢?一种建议是添加一个 JMS 消息侦听器 servlet 来执行这项任务。结果如下图 3 所示。

图 3. 使用 Application Server 运行侦听器
使用 Application Server 运行侦听器的图解

Count 应用程序的元素:

count 客户机与先前一样。因为所有的通信都是通过队列进行,所以它与 Count 应用程序非常松散地耦合在一起。当加载包含 servlet 的 Web 应用程序时,servlet 也被加载并初始化。该 servlet 包含先创建,然后启动侦听器线程的代码。Count 应用程序变成了由下列元素组成的 Web 应用程序:

  • 一个 JMS 消息生产者和消费者 bean
  • 一个计数逻辑 bean

servlet 的 init 方法启动侦听器,它的代码与下面相似:


public class JMSMessageListenerServlet
   extends javax.servlet.http.HttpServlet {
    
   // class MessageListenerThread implements MessageListener and Runnable
   private MessageListenerThread messageListener; 
    
   public void init() throws javax.servlet.ServletException {
      // ...
      messageListener = new MessageListenerThread( /*...*/);
      Thread thread = new Thread(messageListener);
      thread.start();
      // ...
   }
}
    

在开发这样一个解决方案之前,请查阅 WebSphere InfoCenter 的 Section 4.6.3.4来了解从安装在 Application Server 上的用户应用程序调用某些 JMS API 的限制。您或许能够通过开发一个定制的服务来达到相似的效果。 WebSphereInfoCenter 中的 Section 4.10说明为应用程序服务器创建一个定制的服务配置,使得应用程序服务器启动时初始化并启动定制的服务类是有可能的。

由于这个解决方案需要一个应用程序服务器,使用 Application Server JNDI 上下文进行绑定和查找就变得比较有意义。不幸的是,JMSAdmin 并不更新 AEs 配置文件,每次 WebSphere 测试环境停止时,JMSAdmin 定义的绑定都会丢失。如果不在每次启动服务器时运行 JMSAdmin,您可以继续使用文件系统运行 JMSAdmin。另一方面,文件系统 JNDI 服务在解析由子上下文限定的名称时也有问题。例如,名称“Sample/JMS/Q1”指的是子上下文 Sample 的子上下文 JMS 中的对象 Q1。在 Application Server 中查找“Sample/JMS/Q1”是可以的,但您可能必须简化(flatten )该名称,比如将其简化为“JMSQ1”,以便文件系统使用。

这个清单显示如何从文件系统和 Application Server JNDI 上下文中检索队列:

使用文件系统 JNDI 上下文


try {
   // set up a JNDI File system context
   Hashtable p = new Hashtable();
   p.put(Context.PROVIDER_URL, "file://d|/temp");
   p.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
   Context ctx = new InitialContext(p);
   // Obtain the connection factory from JNDI
   queue = (Queue)ctx.lookup("Sample/JMS/Q1");
} catch (NamingExceptin ne) { /* ... */ }
    

使用 Application Server JNDI 上下文


try {
   // set up a WebSphere Application Server JNDI context
   // defaults to 
   //  URL = "iiop://localhost:900"
   //  INITIAL_CONTEXT_FACTORY = com.ibm.
   //   websphere.naming.WsnInitialContextFactory
   Context ctx = new InitialContext( );
   // Obtain the connection factory from JNDI
   queue = (Queue)ctx.lookup("JMSQ1");
} catch (NamingExceptin NE) { /* ... */ }
    

本文没提供用来演示这个解决方案的样本程序。原因是 EMS 提供了一个服务(这个服务不仅执行初始化程序 servlet 的功能,而且能以更健壮的方式执行),并且提供了到 MDB 的迁移路径。如果您没有 EEX 或 Application Developer IE,建议您将初始化程序 servlet 作为一种临时解决方案。它不需要 EJB,可能比较适合不需要在 Java 事务性上下文中访问 MOM 系统的应用程序,或者在开发 EJB 方面还不熟练的开发小组。

如果您按照这种模式开发一个解决方案,那么请构建一个程序作为 Application Developer 或者 Application Developer IE 中的 Web 模块。为了部署,把含有您的 Web 模块的企业应用程序导出到 EAR 文件,并象安装其它 EAR 文件那样在 Application Server AEs 或 AE 上安装这个 EAR。除使用 Application Server JNDI 服务运行 JMSAdmin 之外,在部署和安装方面没有任何与 JMS 相关的注意事项。

第 3 阶段. 使用 EMS 侦听器
改进 Count 应用程序的下一阶段需要用于开发的 Application Developer IE 和用于生产的、安装了 EEX 的 Application Server AEs 或 AE。EMS 功能部件包括由 Application Server 初始化和管理的侦听器。当 EMS 侦听器代替第 2 阶段的 servlet 时,应用程序设计就会如下图 4 所示。

图 4. 使用 EMS 侦听器
使用 EMS 侦听器的图解

Count 应用程序的元素:

EMS 侦听器接收消息并将它们传送到被称为消息 bean 的会话 EJB。该消息 bean 代替消息消费者。计数逻辑是一个会话 EJB。响应生产者可以是另一个会话 EJB。为简单起见,样本作为逻辑 EJB 中的一个方法实现,于是就有了两个 EJB:

  • 消息 bean
  • 计数 bean

Application Server 根据您在配置文件中提供的描述符提供侦听器。它将消息传递到您写的消息 bean,于是消息 bean 变成了进入您代码的入口点。

要为应用程序创建侦听器:

  1. 最容易的方法是从一个样本配置 XML 文件开始并在您选择的一个文本文件编辑器中编辑它。包含进一节,用 <listener> ... </listener> 标记作为各个侦听器之间的定界符。关于这个文件的详细描述,请参阅 Application Server EEX InfoCenter 中的“JMS Listener”。
  2. 把配置文件存储在文件系统上。在 Application Server 管理控制台中,配置 EMS 时提供这个文件的名称和绝对路径。

样本计数应用程序的配置文件只定义了一个侦听器,如下所示。 HomeJNDIName 标记给出了消息 bean 的 JNDI 名称。连接工厂和目标(消息源)也是由 JNDI 名称来指定。当配置 EMS 时,这些名称被绑定到 Application Server JNDI 名称空间。


<Config>
<!--Pooling set MQ connection pooling options-->
<Pooling>
   <!--milliseconds
   <Timeout>100636</Timeout>
   <Threshold>36</Threshold>
</Pooling>
<Listener>
   <HomeJNDIName>samples/jms/CountMessageHome</HomeJNDIName>
   <JMSConnectionFactory>jms/Sample/QCF</JMSConnectionFactory>
   <JMSDestination>jms/Sample/Q1</JMSDestination>
   <MaxRetries>6</MaxRetries>
   <MaxSessions>1</MaxSessions>
</Listener>
</Config>
    

要创建一个消息 bean:

  1. 按照 Application Server EEX InfoCenter 中列出的指示,编写一个会话 EJB。主要标准是消息 bean 应该是一个有效的会话 bean 并且有一个带原型的方法:
    
    public void onMessage( javax.jms.Message)
                

    onMessage 方法应该只执行接收消息的任务,然后将消息传送到业务逻辑 EJB。

  2. 不要创建客户机,也不要编写任何访问这个消息 bean 的代码,因为这个类唯一的客户机应该成为侦听器。

计数消息 bean 如下所示:


public class CountMessageBean implements javax.ejb.SessionBean {

// usual EJB methods create(), getSessionContext(), ...

   public void onMessage(javax.jms.Message msg) {
      System.out.println("onMessage()");
      try {
         String text = ((TextMessage) MSG).getText();
         System.out.println("MSG received: " + text);
//call the business logic
         Context ctx = new InitialContext();
         Object objref = ctx.lookup("java:comp/env/Count");
         CountHome home = (CountHome) PortableRemoteObject.narrow(
            objref,CountHome.class);
         Count bean = home.create();
         bean.doCountLogic(text);
      } catch (JMSException je) {
         // handle exception
      }
   } 
}
    

计数逻辑 bean 是一个普通的会话 EJB。要减小样本的大小,在同一个 EJB 中将消息响应生产者编码为一个方法。响应消息生产者在 JNDI 名称空间中查找连接工厂和应答队列。如下面的代码摘录所示,全局或本地 JNDI 名称都可以。lookup 使用本地名称,而部署描述符 ejb-jar.xmlibm-ejb-jar-bnd.xmi 定义到全局名称的映射。


Context ic = new InitialContext();
// local names
QueueConnectionFactory qcf = (QueueConnectionFactory) ic.lookup(
   "Java:comp/env/QCF");
Queue ioQueue = (Queue) ic.lookup("Java:comp/env/Q2");
// global names
// QueueConnectionFactory qcf = (QueueConnectionFactory) ic.lookup(
//   "jms/Sample/QCF");
// Queue ioQueue = (Queue) ic.lookup("jms/Sample/Q2");
    

这个消息 bean 和任何 EJB 部署描述符中都没有提及队列 Q1,因为它只被侦听器使用并且名称映射由 WebSphere Application Server 管理。

侦听器和名称空间
JMS 连接工厂和目标的名称可能出现在 EMS 名称空间、应用程序 EJB 本地名称空间、Application Server 全局名称空间和外部名称空间(比如文件系统)内。

关于技术上的技巧, Configuring WebSphere MQ JMS support in the WebSphere J2EE Environment很好地描述了这些名称空间之间的关系。

下面的图 5 显示了 Count 应用程序中的名称。因为与 count 应用程序使用相同的 JNDI 服务器,所以 count 客户机也被包括在内。但是,在实际情形中,侦听器接收到的消息的始发者(originator)可以是任何将消息放在队列上的程序。

设置名称使它们映射到名称空间成了配置 EMS 侦听器时最容易出错的地方。文件系统名称空间已经表现出了一些意外的行为,包括在子上下文中解析名称的问题。可能还要有一些反复试验和错误。

图 5. EMS 的 JNDI 绑定
EMS 的 JNDI 绑定图解

在这个阶段,好象不一定要使用文件系统名称空间。在向 AE 部署时,应该除去它。WebSphere 测试环境的要点是 Application Server 启动时必须定义 JMSAdmin 绑定。因为 JMSAdmin 绑定在 AEs 中不是持久存在的,您需要将这些绑定存储在外部名称空间中。幸运的是,当 Application Server 访问 EMS 的文件系统名称空间时,它正确解析子上下文,您不必再通过简化(flatten)JNDI 名称来除去子上下文。使用缺省的构造函数创建 JNDI 上下文。JNDI 查找首先去Application Server 名称空间,然后可能被重定向到您在配置 JMS 提供者时指定的外部名称空间。

运行消息 bean 样本
随本文提供的 CountApp.ear 文件包含已部署的 count 侦听器和计数应用程序 bean。侦听器配置文件 jmsconfig.xml 和客户机 JAR 文件 Stage3.jar 也在下载 ZIP 文件中。

samples.count.stage3.CountClient 不是一个 EJB 客户机应用程序,而是 WebSphere MQ 的一个客户机。它与第 1 阶段的客户机不同,因为它使用的是 Application Server JNDI 名称空间。如果您运行第 1 阶段的样本,您也可以用 samples.count.stage1.CountClient 测试侦听器。您甚至还可以使用 MQ Explorer 将消息放在队列 Q1 上,并在队列 Q2 上看到响应。

如果您不运行第 1 阶段的解决方案,请在 WebSphere MQ 中创建队列,如 运行第一个计数样本程序中所述。您需要在 JNDI 中定义额外的对象,因为这个样本使用 JNDI 子上下文。

  1. 打开 Application Developer IE 并转到 Java 透视图。
  2. JMSsamples 工程运行 JMSAdminFS 。在 InitCtx提示符下,输入:
    
    define ctx(Sample/JMS)
    chg ctx(Sample/JMS)
    define queue(Q1) queue(Q1)
    define queue(Q2) queue(Q2)
    define QCF(QCF)
    display ctx
    end
                
  3. 打开 J2EE 透视图并导入 CountApp.ear 。您可以将Enterprise Application 工程命名为 Stage 3。导入操作使您得到一个包含 CountMessageCount 这两个 EJB 的 EJB 工程 CountAppEJB
  4. 打开 Java 透视图并将 Stage3.jar 导入到 JMSsamples 工程。请参阅一个新包 samples.count.stage3
  5. 打开 Server 透视图。创建一个新的服务器实例和 WebSphere 测试环境配置。您可以调用服务器 Stage3并把它放到名为 TestServers的服务器工程中。
  6. 将工程 Stage3 添加到 Stage3 服务器配置。
  7. 双击 Sever Configuration Stage3,在编辑器视图中编辑它的配置。在 General选项卡上,确保选中了 Enable Administrative Client复选框。保存,然后关闭编辑器。
  8. 启动服务器。

要配置 EMS,就象您在单独安装 AEs 时一样,必须使用瘦管理客户机。缺省情况下,管理应用程序侦听端口 9090。要修改配置,请在调用管理客户机时指定服务器配置文件。文件 server-cfg.xml 位于 Application Developer IE 工作区内,该工作区在服务器工程的服务器配置文件夹中。

  1. 在 Server 透视图中打开 Web 浏览器。转到下面的 URL,其中 <WSADIE> 是安装 Application Developer IE 的目录。如果 <WSADIE> 路径中有空格,请用 %20 替换这些空格,就象 Program%20Files 中这样。

    http://localhost:9090/admin/edit?configFile=
       
    <WSADIE>workspace/TestServers/Stage3.wsc/server-cfg.xml

    在看到提示时提供任意用户标识。

  2. 展开管理客户机左边的 Resources,然后单击 JMS Providers
  3. 单击 New。选择资源提供者类型 IBM MQSeries (Local WebSphere Naming Context)。单击 Next
  4. 填写表单,完成后单击 OK
    注释
    Server Class Path < MQJAVA>/lib/com.ibm.mq.jar;
    < MQJAVA>/lib/com.ibm.mqjms.jar;
    < MQJAVA>/lib/fscontext.jar;
    < MQJAVA>/lib/providerutil.jar;

    输入在一行上。

    < MQJAVA> 是MQ_JAVA_INSTALL PATH 变量的值。

    如果类路径超过了 256 个字符,请将 JAR 文件复制到一个目录来缩短它。

    Name IBM MQSeries (External File System Naming Context) 
    External Initial Context Factory com.sun.jndi.fscontext.RefFSContextFactory 
    External Provider URL file: //< directory> < dir> JMSAdmin.config 中使用的目录,例如,file: //D:/temp
  5. JMS Providers下,选择 IBM MQSeriesJMS Connection Factory,并单击 New
  6. 填写表单,完成后单击 OK
    Name QCF
    JNDI Name jms/Sample/QCF
    External JNDI Path Sample/JMS/QCF
    Connection Type queue
  7. 同样,创建两个 JMS 目标。
    Name Q1
    JNDI Name jms/Sample/Q1
    External JNDI Path Sample/JMS/Q1
    Connection Type queue


    Name Q2
    JNDI Name jms/Sample/Q2
    External JNDI Path Sample/JMS/Q2
    Connection Type queue
  8. 将 Extended Message Service 配置为一个定制服务。首先,从 ZIP 文件解压缩 jmsconfig.xml ,并记录它驻留在何处。
  9. 展开下面这棵树:

    Stage3
       Nodes
          Localhost
             Application Servers
                Default Server
                   Select Custom Servicesand click New

  10. 填写表单,完成后单击 OK
    Display Name Extended Message Service
    Enable selected
    Classname com.ibm.cmm.listener.JMSListenerStub
    External Configuration URL < Absolute Path>/jmsconfig.xml
  11. 将您的服务器配置保存到 <WSADIE> workspace/TestServers/Stage3.wsc/server-cfg.xml
  12. 关闭浏览器视图。

现在,您准备好测试侦听器了。

  1. 停止 Stage 3 服务器并重新启动它。代码行可能与控制台中的下列代码相似:
    
    *** Starting the server ***
    ...
    ************* End Display Current Environment *************
    xxx Server U Version : 4.0.2
    xxx Server U Edition: Advanced Single Server Edition
       for Multiplatforms
    ...
    xxx ResourceBinde I WSVR0049I: Binding QCF as jms/Sample/QCF
    xxx ResourceBinde I WSVR0049I: Binding Q1 as jms/Sample/Q1
    xxx ResourceBinde I WSVR0049I: Binding Q2 as jms/Sample/Q2
    xxx Server I Setting JNDI name for JMS in resRef binding
       as local:jms/jms/Sample/QCF
    xxx Server I Setting JNDI name for JMS in resRef binding
       as local:jms/jms/Sample/Q2
    xxx EJBEngine I WSVR0037I: Starting EJB jar: CountAppEJB
    ...
    xxx JMSListenerSt I WMSG0042I: MDB Listener
       samples/jms/CountMessageHome started successfully 
       for JMSDestination jms/Sample/Q1
    xxx JMSListenerSt A WMSG0001I: JMS Listener started successfully
    xxx Server A WSVR0023I: Server Default Server open for e-business
                
  2. 转到 Java 透视图并运行 JMSsamples 工程内的 samples.count.stage3.CountClient 应用程序。期望输出为:
    
    Retrieving JMS objects from JNDI
    sending message <Hello World!> to queue:///Q1
    Unable to load message catalog - mqji
    message sent
    Getting message from queue:///Q2
    Message <Hello World!: Contains 12 characters> received
                
  3. 在 Debug 透视图中,您可以选择 Server launcher process 来查看 Count 应用程序的跟踪输出。它应该包含下面的代码行:
    
    onMessage()
    MSG received: Hello World! 
    Putting message: Hello World!: Contains 12 characters on queue:///Q2
                

在 WebSphere Application Server 上安装消息 Bean 样本
您可以将该样本安装在已经安装了 EEX 的 AE 或 AEs 上。简要过程有下列步骤:

  1. 在 WebSphere MQ 中定义队列。
  2. 运行 JMSAdmin 将队列和连接工厂绑定到一个 JNDI 名称空间。使用 Application Server JNDI 服务器 AE 或者 AEs 的文件系统。倘若您在相关的配置文件中设置了 PROVIDER_URL 的值,您就可以运行 Application Developer IE 中的 JMSAdminWASJMSAdminFS 程序。
  3. 使用提供的 CountApp.ear 。如果想添加安全性设置或者修改 EAR 文件,您可以通过应用程序装配工具(Application Assembly Tool)传送它。
  4. 在 AE 或 AEs 上安装您想安装的任何 EAR 文件。
  5. 将侦听器配置文件放在文件系统上,这样 Application Server 就可以访问它。
  6. 使用 AE 的管理控制台或者 AEs 的管理客户机定义并配置 JMS 提供者和 EMS。在 AEs 上,重复上面描述的配置 WebSphere 测试环境的步骤。用户界面在 AE 上的组织与在 AEs 上不同,但它们在 AE 和 AEs 上的顺序是相同的。
  7. 重新启动管理服务器将所做的更改应用于它的配置。
  8. 通过运行将消息放在与侦听器相连的队列中的这个过程进行测试。

第 4 阶段. 准备消息驱动 Bean
当侦听器和消息 bean 引入 EMS 时,MDB 规范正处在成型阶段。实际上,侦听器和消息 bean 的目的是在标准固定下来之前起到 MDB 的作用。在我们期盼着 J2EE 标准时,IBM 解决方案被用来推动向 MDB 的转换。因此,Count 应用程序的最终版将用 MDB 代替侦听器和消息 bean。

WebSphere Application Server AE 和 AEs 的版本 5 将支持遵守 J2EE 1.3 规范的 MDB。一个 MDB 将执行侦听器和消息 bean 两者的任务。Count 应用程序的第四阶段将如下图 6 所示。

图 6. 消息驱动 Bean
消息驱动 Bean 图解

Count 应用程序的元素:

一个 MDB 代替 EMS 侦听器和消息 bean。它直接调用业务逻辑类。该逻辑应该封装在会话 EJB 中。使用一个单独的发送方 bean 来产生响应消息。如果您在设计消息 bean 时采用推荐的方法,就有望直接从消息 bean 转换到 MDB。本质上的区别有:

  • MDB 没有 home 或远程接口,它只有 bean 类。
  • MDB 实现接口 MessageDrivenBean 而非 SessionBean。
  • 不需要侦听器配置文件。
  • MDB 在 Application Server 上的安装不同于侦听器和消息 bean。

关于事务的简要说明
您可能希望将自己的 JMS 代码封装在会话 EJB 中,您只要一这样做,就会提供一个事务性上下文,消息的产生和消费将在这个上下文中进行。事务这个主题太大,无法在这里讨论。但是,有一些要点非常重要,您不可忽略。

  • 如果一个会话 EJB 在事务作用域内产生了一条消息,那么这个作用域只扩展到将该消息发送到 MOM 系统。接收消息的消费者必须在一个单独的事务中这样做。
  • 如果一条消息被发送到一个队列,而这个队列所在的事务后来被回滚,那么将从队列中除去该消息。相似地,如果从一个队列读取一条消息,且这个队列所在的事务后来被回滚,那么该消息将被返回到队列。
  • 回滚消息的消费操作会产生一个循环的问题:如果消息中的某个东西导致事务失败,那么将消息放回队列意味着它将重复地再次被读,并且将重复地再次中断事务。请在侦听器配置文件中设置 maxtries 值来终止这种循环。
  • WebSphere MQ 可以参与本地事务(在这种事务中,WebSphere MQ 控制事务提交或回滚),或者参与由 Application Server 管理的全局事务。全局事务在启用 XA 的不同企业信息系统如 DB2® 或 CICS® 之间启用两阶段提交。
  • 要在事务中包含 WebSphere MQ 连接,只需使用一个 JMSWrapXATopicConnectionFactory 类型的队列连接工厂。请使用关键字 WSQCF 在 JMSAdmin 中定义这么一个对象,如下所示:
    
    Define WSQCF(FactoryName)
                
  • EMS 侦听器和 MDB 可以初始化事务作用域。因此,在一个事务中,您可以消费消息、执行企业任务并对另一个 EIS 系统比如 CICS 或 DB2 产生作用。下图 7 显示了一个涉及 WebSphere MQ 和 DB2 的全局事务,它们可以添加到 Count 应用程序中。

图 7. 记录消息已收到
记录消息已收到的图解

结束语
本文讨论了使用 ApplicationDeveloper IE 开发 JMS 应用程序,并使用 WebSphere MQ 作为 MOM 系统。它为如何设计消息的异步消费这个问题提供了解决方案。本文提供了四种设计,并提供了其中两种设计的样本代码。

  • 第 1 阶段的解决方案, 编写自己的侦听器,实现起来很简单,并且不需要 Application Server(除非您要包含进 Web 应用程序或添加 EJB)。然而,它是有限制的:侦听器的生命期很难管理,并且您无法为消费消息建立事务性上下文,使事务回滚把消息返回到队列。这个解决方案可以使用 Application Developer 来开发。
  • 第 2 阶段的解决方案, 使用 WebSphere Application Server 启动侦听器,试图改进侦听器生命期的管理。这个解决方案可以在 Application Developer 中开发和测试,并可以安装在 Application Server AEs 或 AE 上。
  • 第 3 阶段的解决方案, 使用 Application Server EMS 中的 EMS 侦听器,现在可以使用 Application Developer IE 来开发。它考虑到了比第1 阶段或第 2阶段健壮得多的解决方案:应用程序服务器管理侦听器,并且消息可以在一个功能齐备的事务上下文中被消费。这种健壮性大大提高的代价是增加了一定程度的复杂性:您必须将消息 bean 作为一个会话 EJB 来开发,并且在往安装了 EEX 的 Application Server AE 或 AEs 上安装应用程序时要执行完所有的附加步骤。
  • 第 4 阶段的解决方案, 使用 MDB,要到 2002 下半年才能实现。它的功能和健壮性并不比第 3 阶段好,但它遵守 J2EE 1.3 规范。由于 MDB 代替了侦听器和消息 bean 这两者,所以应用程序的结构将得到简化,同时由于不需要侦听器配置文件,安装也有可能简化。

JMS 标准的出现和 MDB 规范的完成增加了 MOM 系统在分布式企业应用程序中的使用,同时还鼓励现有的 MOM 用户将旧应用程序转换为符合标准的应用程序。现在您可以开始构建使用 JMS 的应用程序了。本文提出了设计应用程序以便向 MDB 转换的战略。

参考资料

红皮书的订户应该搜索 外部红皮书网站

下载
Name Size Download method
JMSsamples.zip 80 KB HTTP
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值