级别: 初级 Greg Wadley (wadley@us.ibm.com), WebSphere 高级 IT 顾问专家, IBM 达拉斯
2003 年 6 月 01 日 异步消息传递模式是 J2EE 应用程序编程模型的一个重要组成部分。这些模式是为实现应用程序之间的“松散耦合”而提供的,对于流程流、并行处理、独立于时间的处理及事件驱动的处理非常有用。
© Copyright International Business Machines Corporation 2002. All rights reserved.
引言
异步消息传递模式是 J2EE 应用程序编程模型的一个重要组成部分。这些模式是为实现应用程序之间的“松散耦合”而提供的,对于流程流、并行处理、独立于时间的处理及事件驱动的处理非常有用。本文先描述了目前 Java 消息传递 API(Java messaging API)的局限性,然后又介绍了 IBM 在 WebSphere 企业版 V5 中引入的一组被称为扩展的消息传递(Extended Messaing(EM))的新技术。为使消息传递程序在 WebSphere 环境中传递时速度更快,效率更高,EM 结合了开发和运行时方面的许多增强之处。
Java 消息传递服务(Java Messaging Service(JMS))是一个 Java API 集,它提供了一个框架供 Java 程序向异步消息传递系统进行可移植性调用。JMS 标准是由好几家供应商开发的,详情请参阅
JMS 主页。
但 JMS 仍是一种相对比较低级的 API,并要求您为对象查找、消息格式化、数据映射、与消息传递模式相关的事务及管理对象的高速缓存编写代码以实现更好的性能。EJB 2.0 规范引入了消息驱动的 Bean(Message Driven Bean(MDB)),它为事件驱动的消息检索提供容器支持。但仍有许多消息传递模式它们并不处理,比如:
- 发送有响应或无响应的消息
- 应用程序可调用的 receiver bean - 消息需要在应用程序流程中被检索,而不是使用 MDB 检索,后者使消息一到目的地就被处理。
- 发送和接收时的延迟响应。
- 在 JMS 消息中对数据进行映射和重新格式化使程序员能够把 JMSMessage 的详细信息设置为消息的参数,而不必对消息数据进行构建和解析
- 向多个目的地发送消息
本文假设您具备消息传递模式和 EJB 2.0 术语方面的应用知识。
其他封装 JMS API 的尝试
2001 年,Ryan Cox 和我发表了
JMSCommand 第 1 部分和
JMSCommand 第 2 部分,向 JMS API 引入了一个名为 JMSCommand bean 的抽象来简化 JMS 编程。它是一个简化模型,通过属性文件、线程安全 JMS 对象的高速缓存及一个简单的命令模式为接收和发送 JMS 消息提供 JNDI 设置。
尽管 JMSCommand bean 能使 Java 开发者迅速构建 JMS 应用程序,但它无法在 J2EE 容器中运行,因此在功能和范围上受到了限制。此外,它也没有可提供的工具。其他简化 JMS 编程模型的尝试包括来自 Apache 软件基金会(Apache Software Foundation)的一种开放源代码 JMS 抽象,被称为
Messenger。但无论 JMSCommand 还是 Messenger 都无法同时提供出色的运行时和高级工具为处理公共消息传递模式生成必要的管道。
扩展的消息传递功能
下一代消息传递工具和运行时支持就在这里 - 它被称作扩展的消息传递(EM),是随 WebSphere V5 一起提供的。EM 工具包含在 WebSphere Studio Application Developer 集成版 V5(以下称作 Application Developer IE)中,而运行时和配置支持则是 WebSphere Application Server 企业版 V5 的一部分。EM 支持的消息传递模式包括:
- 发送 - 即发即忘
- 需要同步响应的发送(超时为可选项)
- 可选择是否处理延迟的响应
- 接收请求,但无应答
- 接收请求并发送同步应答
- 接收请求并发送异步应答
此外,EM 还提供:
- 简化的编程模型
- 消息部件的数据映射
- MDB 样式及应用程序可调用的消息接收
- 面向线程和群集的容器支持
- 向多个目的地发送消息
- 高级工具
关于构建和配置扩展的消息传递(Extended Messaging)bean 的细节问题,请查看下面的内容。
代码简化
正如上面所讨论的,JMS 编码的问题之一在于您仅为发送一条消息而不得不编写的代码和 J2EE 构造的数量。一个传统的 JMS 应用程序看起来会是这样的:
// Initialize the JNDI context
InitialContext ctx = new InitialContext();
// Look up the QueueConnectionFactory in JNDI
Object o = ctx.lookup("jms/myQCF");
qcf = (QueueConnectionFactory)PortableRemoteObject.narrow(o,QueueConnectionFactory.class);
// Create a QueueConnection
QueueConnection conn = qcf.createQueueConnection();
//Create a QueueSession
QueueSession session =
conn.createQueueSession(false,Session.AUTO_ACKNOWLEDGE);
// Look up the Queue in JNDI
Object o = ctx.lookup("jms/myQ");
q = (Queue) PortableRemoteObject.narrow(o,Queue.class);
// Create a QueueSender
QueueSender sender = session.createSender(q);
// Create a message
Message message = session.createTextMessage();
message.setJMSType("LogMessage");
message.setText("Hello World");
// Send the message
sender.send(message);
|
哇,我们只是要发送一条消息呀,代码太多了!并且前面已经提到过,JMS 并没有为连接工厂、队列、发送方等等的高速缓存提供容器支持 - 所有这一切都必须手工进行。但 EM 使您能够用 Application Developer IE 附带的向导来完成所有这些工作,甚至更多工作。
工具支持
EM 结合了带运行时支持的工具。让我们一起来看看如何用 Application Developer IE 创建一个简单的案例 -“sender bean 和 receiver bean”。Sender bean 向一个队列发送一个字符串,然后在应答队列里等待应答。然后一个 MDB 从请求队列中读取该消息,调用一个会话 bean(它给消息文本添加时间戳记),通过应答队列把消息返还给 sender bean。图 1 详细描述了这个案例:
图 1. 应用程序样本
下面是在 Application Developer IE 中创建这个案例的步骤:
- 使用向导构建一个名为 DoSomething 的无状态会话 bean。
- 添加一个名为 addDateToInput(String) 的方法,然后把该方法作为本地接口。
- 使用 Application Developer IE 的 EM 向导创建 sender 和 receiver bean。
要启动 EMS 向导,请选择一个 EJB 项目然后单击右键:
图 2. 创建 sender 和 receiver bean
当向导完成并且部署的代码被生成时,您将看到下列 bean 被生成:
图 3. 生成的代码
请记住,MySender 是一个向请求队列发送消息的无状态会话 bean。MyReceiver 是一个 MDB,它在请求队列上侦听,接收消息,调用 DoSomething bean 的 addDateToInput() 方法,然后在应答队列上公布已更新的消息。最后,MySender 从应答队列中检索消息,完成整个往返过程。因为 MDB 只能被容器调用,并且没有可调用的接口,所以 MyReceive 既没有本地接口,也没有远程接口。
上面已经声明,这个消息传递示例的所有代码都是由 Application Developer IE 中的向导生成的。对 MySender 会话 EJB 而言,是添加了一个方法来发送提供的消息,然后因应答的超时被堵塞。下面的清单详细显示了该方法。输入参数“message”是在使用 Application Developer IE 中的向导创建 sender bean 时构建的。在消息中您想包含多少部件就可以包含多少。每个部件映射 sendWithResponse() 方法中的一个参数。这个示例有一个 java.lang.String 类型的消息部件用来处理输入消息,这意味着您不必构建一个复杂的消息来发送,然后在相应的 MDB 中解析它。CMMFormatter 通过向“formatter”对象添加参数,然后调用 getMessage() 返回绑定的消息来自动完成此项工作。然后 EM 使用 CMMParser 对象解析响应消息。
/**
* Generated - maps to a JMS message and sends with response
*/
public java.lang.String sendWithResponse(long timeout, java.lang.String message)
throws CMMException
{
// Create sender based on the defined output port
CMMSender sender = CMMFactory.createSender("ems/MyOutputPort");
try {
// Create message factory
MessageFactory factory = sender.getMessageFactory();
// Create formatter
CMMFormatter formatter = CMMFactory.createCMMFormatter(factory);
// Add parameters to the message
formatter.addStringParameter(message);
// Get the message
Object request = formatter.getMessage();
// Set message type
sender.setRequestMessageType("MyMessageFormat");
// Send request receive response
Object response = sender.sendRequestReceiveResponse(request, timeout);
// Create parser
CMMParser parser = CMMFactory.createCMMParser(response);
// Process exception
if (parser.containsException()){
try{
throw parser.getException();
}
catch(CMMException exc){ throw exc;}
catch(Exception exc){ throw new CMMException("Unexpected Exception", exc);}
}
// Extract response and return
return parser.getStringParameter();
}
finally
{
sender.close();
}
}
|
在消息被 MySender bean 的 sendWithResponse() 方法发送后,它将被发往输出端口中定义的目的地(队列)。然后该消息将被 MDB(MyReceiverBean) 获得,通过调用 DoSomething bean 的 addDateToInput() 方法对其进行解析和更新,然后放回到应答目的地。这段代码(如下所示)又是 Application Developer IE 向导生成:
public void onMessage(javax.jms.Message msg) {
try {
//Select based on the message type
If ("MyMessageFormat".equals(msg.getJMSType())){
try {
// Create reply sender
CMMReplySender replySender = CMMFactory.createReplySender(msg);
MessageFactory factory = replySender.getMessageFactory();
// Create formatter
CMMFormatter formatter = CMMFactory.createCMMFormatter(factory);
try {
// Create parser to extract parameters from the message
CMMParser parser = CMMFactory.createCMMParser(msg);
// Extract parameters
java.lang.String param0 = parser.getStringParameter();
// Create EJB
javax.naming.Context initialContext = new javax.naming.InitialContext();
DoSomethingLocalHome home =
(DoSomethingLocalHome)initialContext.lookup("java:comp/env/ejb/DoSomething");
DoSomethingLocal obj = home.create();
// Invoke target method
String reply = obj.addDateToInput(param0);
// Set reply
formatter.addStringParameter(reply);
}
catch(Exception exc) {
// Set exception
formatter.setException(exc);
}
Object reply = formatter.getMessage(); // Send reply
replySender.sendReply(reply);
return;
}
catch(Exception exc) {
// Handle exception
CMMFactory.handleException(msg, exc);
return;
}
}
}
catch (JMSException exc){
// Failed to get message type
}
CMMFactory.handleMessage(msg);
}
|
在使用向导创建 MDB 期间,您可以让 MDB 调用会话 bean 的一个方法。该选项创建调用上面清单中显示的 addDateToInput(param0) 方法的代码。这使您能够在仍使用向导创建 receiver bean 时把流程的业务逻辑和消息流分开,如图 4 所示:
图 4. 创建对会话 EJB 的调用
在您使用 Application Developer IE 构建好 sender 和 receiver bean 后,您可以在 Application Developer IE WebSphere Enterprise Test Environment 或 WebSphere Application Server 企业版中部署和运行它们。
运行时配置
在使用 Application Developer IE 完成发送和接收消息传递模式后,您需要使用 WebSphere V5 管理控制台配置相应的消息传递组件。这些组件包括:
- QueueConnectionFactory
- 请求(Request)队列和应答(Reply)队列
- EMS 输入端口和输出端口
- MessageListnerPort
使用输入和输出端口作为目的地和连接工厂的抽象是为已经添加的部署时配置提供的。由于一个端口可以作为多个目的地的抽象,因此发送方可以自动向多个目的地发送消息。关于设置运行时配置的详细信息,请参阅 Application Developer IE 联机帮助。
结束语
JMS 和 MDB 提供了一个独立于平台的向 J2EE 应用程序添加消息传递的实现,但异步消息传递模式许多方面的问题它们都没有解决。支持 EM 的 Application Developer IE 向 WebSphere Application Server V5 提供的 J2EE 1.3 基础架构同时提供工具扩展及运行时扩展,使您通过编写极少的代码或者不必编写代码就可以构建健壮的异步应用程序。EM 还通过让接收方或者 MDB 调用会话 bean 的方法去处理消息来促进业务逻辑与消息传递管道的分离。
关于作者  | |  |
Greg Wadley是德克萨斯州 Kennedale 的一位 IBM IT 专家。他为 WebSphere Application Server、WebSphere Portal Server 及 WebSphere Studio Applicaiton Developer 提供技术销售支持。您可以通过
wadley@us.ibm.com与 Greg 联系。
|
对本文的评价
|