消息中介的实用介绍——第 5 部分:中介请求/响应样式的消息交互
2009-10-21 00:00:00 来源:WEB开发网引言
如果阅读过本系列中的前几篇文章,那么将熟悉如何创建和使用消息中介,消息中介是对 IBM WebSphere Application Server V6 的消息传递功能的一种新的编程扩展,可以简化使用消息传递的系统、服务、应用程序或组件之间的连接。这几篇文章以及 IBM 的同事最近撰写的其他一些文章,都主要关注单向的消息传递模式。然而,也常常为 Java™ Messaging Service (JMS) 和其他的应用程序实现请求/响应样式的语义。JMS 规范甚至提供了 QueueRequestor 和 TopicRequestor 类来简化这种动态的消息传递。在本系列的第 5 部分中,我们将介绍如何使用消息中介来处理请求/响应消息。
考虑到本文的目的,我们将看到的应用程序示例是一个库存管理系统,它将发送查询以确定仓库中某种产品的存货数量。将在响应中发送应答该查询的信息。本文重用了为第 4 部分:通过中介和 XSLT 修改消息编写的中介。
中介回顾
让我们先来回顾一下 WebSphere Application Server 的上下文中,中介和消息格式的含义。
中介处理程序是与消息传递目的地相关联的一个 Java 类,在向该目的地发送消息的时候将调用其中介方法。中介处理程序可以修改、重新路由或甚至删除该消息。
有几种类别的消息可以传递给中介处理程序。中介处理程序通过使用 getFormat 方法获取消息格式,以确定传递给它是何种类别的消息。该格式的可能的取值包括:
格式 | 描述 |
JMS:byte | 包含字节数组的 JMS 消息。 |
JMS:text | 包含字符串的 JMS 消息。 |
JMS:object | 包含使用 java.io.ObjectInputStream 可将其反序列化为对象的字节数组的 JMS 消息。 |
JMS:stream | 包含类型化数据流的 JMS 消息。 |
JMS: | 无内容的 JMS 消息(通过构造一个 javax.jms.Message 对象而不是任何特定的 JMS 消息类型来创建)。 |
SOAP: | 往返 Web 服务的 SOAP 消息。 |
Bean: | 往返 Web 服务的消息。 |
消息传递中的请求/响应处理
消息传递提供者通常并不提供对请求/响应消息传递的直接支持,但是有三种经常使用的常规方法:
使用临时应答队列。在这种模型中,请求者创建了一个临时目的地,将响应目的地的地址放入请求消息中,然后发送该消息。然后响应者将响应发送到这个目的地。JMS 应用程序常常使用这种模型,并且该模型的缺点是将单个请求与响应关联起来,每个请求都需要使用一个临时目的地。要解决这种限制,通常为每个客户端使用单独的临时目的地,然后使用后面的两个要点中所描述的机制来关联单个请求和响应。
将 Message ID 复制到 Correlation ID。 在这种模型中,发出请求的应用程序在一个已知的队列上侦听响应。要么请求者和响应者都知道这个队列,要么请求者在请求消息中指定应答发送到何处。然后,做出响应的应用程序将该请求的消息 ID 复制到响应的相关 ID。请求者随后使用相关 ID 中的内容对响应进行筛选。服务集成总线中的入站、出站和网关服务函数使用了这种模型。WebSphere MQ 应用程序也常常使用它。
将 Correlation ID 复制到 Correlation ID。除了请求者在请求消息中添加了一个相关 ID 之外,这种模型和前面的模型是相同的。然后,响应者将请求消息的相关 ID 复制到响应消息的相关 ID。WebSphere MQ 应用程序常使用这种模型。
总线中的请求/响应处理
总线本身并不具有对请求/响应处理的直接支持,但它为方便地构建请求/响应处理提供了支持。
每个消息都包含了在其通过总线时应该访问的目的地列表(或路径)。这就是转发路由路径,并且通常将发送带有包括单个目的地的路径的请求消息。另外,可以使用缺省转发路由路径对目的地进行配置,如果到达目的地的请求消息没有转发路由路径,那么该缺省转发路由路径将放入到其中。
消息中还包含了一条反向路由路径。尽管在消息通过总线时不会对它进行处理,但该路径通常来自于请求消息并设置为响应消息的转发路由路径。在这种方式中,响应消息在其通过总线时所使用的路由可以受到请求消息处理的影响。
如果要求响应,那么请求消息将设置反向路由路径。每个目的地可以指定一个应答目的地。在消息经过该目的地时,将为反向路由路径(如果已经存在一项)预先计划这个目的地。
通过中介传递请求/响应消息
作为一条通用规则,在总线中通过中介传递请求/响应消息交互并不比通过中介传递单向消息困难多少。如果需要通过中介对请求消息进行传递,在响应路径上可能需要某种形式的中介。一个简单的例子是将消息从 COBOL Copybook 转换为 XML,如果响应者只能理解 XML,而请求者只能理解 COBOL,那么请求和响应消息都需要进行转换。
当通过中介传递请求消息时,如果需要,响应中介可以应用许多方式:
创建可以区分消息是请求还是响应的中介,并进行适当的操作。可以将中介所应用的目的地添加到请求消息的反向路由路径。这就意味着,响应消息将通过中介目的地返回。
创建两个可以创建的中介:一个请求中介和一个响应中介。它们分别应用于两个不同的目的地。请求消息使用一个,而响应消息使用另一个。请求消息可以只需将请求中介的目的地添加到反向路由路径,或者只需应用应答目的地的反向中介。
创建不能区别差别的单个中介。在这种情况下,可以对请求和响应路径应用同一个中介,并执行相同的逻辑,但是不能够根据它所部署的目的地来进行参数化。
这些方法中最简单的实现是最后一种,而最复杂的是第一种。通过利用目的地的应答目的地字段,请求中介不需要做任何事情就可以将响应路由到其伙伴响应目的地。这在使用一般中介(比如 XSLT 中介,需要应用它来对请求和响应分别执行不同的 XSLT 中介)时特别有用。
设计决定
为应用程序和环境选择适当的请求/响应模型是非常重要的。考虑到本文的目的,我们将使用上面列出的第一种和第二种模型的组合形式。在我们的模型中,客户端将为所有的请求创建一个临时目的地,并通过将消息 ID 复制到相关 ID,建立单个请求和响应之间的关联。
我们将浏览一个比较简单的请求/响应中介的示例,介绍如何在无需编写任何代码的前提下执行它。红皮书 Patterns:SOA with an Enterprise Service Bus in WebSphere Application Server V6 提供了关于聚合和分解消息的复杂示例的详细信息。
该示例将利用一个简单的 JMS 应用程序来提供提出请求的客户端和做出响应的服务。它可能出现在现实的解决方案中,其中响应应用程序是与客户端应用程序不同的某种形式的企业应用程序。
JMS 请求消息
JMS 应用程序使用清单 1 所示的 XML 向 requestQueue 发送 JMS BytesMessage。
清单 1. JMS 客户端生成的示例 XML 文档 <?xml version="1.0" encoding="utf-8"?>
<stockQuery>
<item number="1234"/>
</stockQuery>
JMS 应用程序的响应部分需要清单 2 所示格式的 XML。
清单 2. 响应 JMS 应用程序所需的示例 XML 文档 <?xml version="1.0" encoding="utf-8"?>
<stockQuery>
<item>1234</item>
</stockQuery>
需要在请求消息到达响应应用程序之前对其进行转换。清单 3 中所示的 XSLT 将执行该转换任务。
清单 3. 用来转换两个请求 XML 文档的 XSLT <?xml version="1.0" encoding="utf-8"?>
<stockQuery xmlns:xslt="http://www.w3.org/1999/XSL/Transform">
<item><xslt:value-of select="/stockQuery/item/@number"/></item>
</stockQuery>
JMS 响应消息
响应 JMS 应用程序在 JMS BytesMessage 中向请求消息中指定的 JMSReplyTo 发送响应。它把清单 4 所示的 XML 放入到响应消息中。
清单 4. 响应应用程序生成的示例 XML 文件 <?xml version="1.0" encoding="utf-8"?>
<stockAvailability>
<quantity>2</quantity>
</stockAvailability>
JMS 应用程序客户端部分需要清单 5 所示格式的 XML。
清单 5. 请求应用程序所需的示例 XML 文档 <?xml version="1.0" encoding="utf-8"?>
<stockAvailability>
<item quantity="2"/>
</stockAvailability>
需要在响应消息到达客户端应用程序之前对其进行转换。清单 6 所示的 XSLT 将执行该转换任务。
清单 6. 用来转换两个响应 XML 文档的 XSLT <?xml version="1.0" encoding="utf-8"?>
<stockAvailability xmlns:xslt="http://www.w3.org/1999/XSL/Transform">
<item><xslt:attribute name="quantity"><xslt:value-of select="/stockAvailability/quantity"/>
</xslt:attribute></item>
</stockAvailability>
当然,该示例比较简单,而在现实应用程序中需要进行更有价值的转换任务。例如,在请求上将 SOAP 转换为 COBOL Copybook,而在响应上则将 COBOL Copybook 转换为 SOAP。
简单的请求/响应中介
这里,我们将展示如何构建请求/响应中介,尤其是如何能够在不需要编写任何代码的情况下构建这些中介。
初始配置
该示例需要创建某些资源,只需使用下载资料中所包含的 createInitialResources.jacl 脚本即可完成这项工作。该脚本将创建这些表格中列出的资源:
将创建具有单个总线成员的总线:
创建总线时的常见错误
在创建总线时(特别是在非联合应用程序服务器上),常常会忘记添加一个总线成员。我总是犯这个错误。
字段 | 值 |
名称 | dwBus |
将在不同的对象中创建两个队列类型的目的地:
字段 | 值 |
标识符 | dwRequestQueue |
标识符 | dwResponseQueue |
单个中介:
好的做法
无论在什么地方,对中介和处理程序列表名都使用相同名称。这样可以避免在调试中介的消息流时出现过多的混淆。
字段 | 值 |
名称 | dwXSLTMediation |
中介处理程序列表名 | dwXSLTMediation |
然后通过 dwXSLTMediation 传递发送到目的地 dwRequestQueue 和 dwResponseQueue 的消息。
对于提供的 JMS 应用程序,将使用缺省消息传递提供者来定义下面的 JMS 资源。
JMS 连接工厂 (connection factory):
字段 | 值 |
名称 | dwCF |
JNDI 名称 | jms/dw/CF |
总线名称 | dwBus |
JMS 队列:
字段 | 值 |
名称 | dwDest |
JNDI 名称 | jms/dw/dest |
总线名称 | dwBus |
队列名称 | dwRequestQueue |
为请求/响应处理进行配置
我们现在已经设置了初始配置,这使得我们可以修改并启用请求/响应处理。(要创建这些基本资源,请参阅本系列中的前几篇文章,如参考资料中所列。)
第一项更改是设置请求目的地上的应答目的地名称:
在 WebSphere Application Server 管理控制台中,展开 Service integration => Buses。
选择总线 dwBus。
在 Destination Resources 下选择 Destinations。
选择目的地 dwRequestQueue。
向下滚动直至可以看到 Reply destination 字段,并如图 1 所示输入 dwResponseQueue。
图 1. 配置应答目的地
单击 OK。
配置上下文属性时的常见错误
在配置上下文属性时,常常没有指定上下文的类型。如果设置了不正确的上下文类型,那么要么数据对中介而言不可用,要么它的类型不正确(导致 ClassCastException)。将会出现哪一种情况,取决与其不匹配的程度。
下一步是配置 XSLT 中介,以使它应用正确的转换任务。可以通过中介传递上下文属性或者目的地上下文属性来指定中介所应用的 XSLT。可以在目的地或中介上的管理控制台中设置这些属性。然后,它们将作为属性放入到 SIMediationContext 中。对于本示例,对两个不同的目的地(其中每个目的地需要应用不同的 XSLT)应用了相同的中介。这意味着,目的地上下文属性是指定该信息的适当的位置。
在管理控制台中,展开 Service integration => Buses。
选择总线 dwBus。
在 Destination Resources 下选择 Destinations。
选择目的地 dwRequestQueue。
选择链接 Context Properties。
单击 New。
输入或选择下面的值(图 2):
名称: Stylesheet
上下文类型: 字符串
上下文值: <?xml version="1.0" encoding="utf-8"?><stockQuery xmlns:xslt="http://www.w3.org/1999/XSL/Transform"><item><xslt:value-of select="/stockQuery/item@number"/></item></stockQuery>
图 2. 配置请求中介 XSLT
图片看不清楚?请点击这里查看原图(大图)。
对于指定第二个 XSLT 的响应目的地,也需要完成这项任务。
在管理控制台中,展开 Service integration => Buses。
选择总线 dwBus。
在 Destination Resources 下,单击 Destinations。
选择目的地 dwResponseQueue。
选择链接 Context Properties。
单击 New。
输入或选择下面的值(图 3):
名称: Stylesheet
上下文类型: 字符串
上下文值: <?xml version="1.0" encoding="utf-8"?><stockAvailability xmlns:xslt="http://www.w3.org/1999/XSL/Transform"><item><xslt:attribute name="quantity"><xslt:value-of select="/stockAvailability/quantity"/></xslt:attribute></item></stockAvailability>
图 3. 配置响应中介 XSLT
图片看不清楚?请点击这里查看原图(大图)。
XSLT 中介
该应用程序中使用的 XSLT 中介对前面的文章中提供的中介(在消息的副本上执行 XSLT)稍微进行了一些改进。本示例中使用的中介对要通过中介传递的消息执行 XSLT。用清单 7 所示的代码替换 XSLTMediationHandler 类中 innerHandle 方法的代码。该中介存储于 MediationPart5MediationEAR.ear 文件中,也包括在下载资料中,并且需要安装到服务器中。
清单 7. 经过修改的 innerHandle 方法的内容 // Retrieve the message from the context
SIMessage message = getContext().getSIMessage();
// Get the message contents in the form of a JMS Bytes message
try
{
// Get a new datagraph for the new message we're about to build - Point 1
DataGraph graph = message.getNewDataGraph(SIApiConstants.JMS_FORMAT_BYTES);
DataObject body = graph.getRootObject();
if (body.isSet("data"))
{
// Grab the bytes
byte[] payload = body.getBytes("data/value");
// Transform the bytes
payload = XSLTTransform.transform(payload,getStringProperty(STYLESHEET_PROPERTY_NAME));
// Replace the payload in the data graph
body.setBytes("data/value",payload);
message.setDataGraph(graph, SIApiConstants.JMS_FORMAT_BYTES);
}
else
{
// No bytes sent, so throw an IllegalArgument so that the message
// will be sent to the exception destination
IllegalArgumentException e = new IllegalArgumentException("No body to the message");
throw e;
}
}
catch(Exception e)
{
// Hmm, we had a problem somewhere doing the transformation or maybe the send
e.printStackTrace();
throw new MessageContextException(e);
}
// If we get here, everything worked and the tracking message has been sent
return true;
运行测试应用程序
关于 JMS BytesMessage 的警告
在发送包含 XML 数据的 JMS BytesMessage 时,请避免使用所提供的 writeUTF 方法,因为这种 UTF 编码添加了额外的数据,那么消息将不以字符串中的第一个字符开头。因此,在读取中介中的字符串时必须加以小心。(我们的示例并没有使用这个方法。)
在运行应用程序之前,保存配置并重启该应用服务器是非常重要的。在完成该任务后,通过执行这个命令来运行示例应用程序:launchClient.bat MediationPart5ClientEAR.ear 5。这将出现图 8 所示的输出。
该测试应用程序是一个简单的 JMS 应用程序,它同时扮演了客户端和服务器,并且使用单个数值作为参数。
清单 8. 运行测试应用程序得到的输出IBM WebSphere Application Server, Release 6.0
J2EE Application Client Tool
Copyright IBM Corp., 1997-2004
WSCL0012I: Processing command line arguments.
WSCL0013I: Initializing the J2EE Application Client Environment.
WSCL0035I: Initialization of the J2EE Application Client Environment has completed.
WSCL0014I: Invoking the Application Client class mediation.messages.part5.Main
Sending request message: <?xml version="1.0" encoding="utf-8"?>
<stockQuery>
<item number="5" />
</stockQuery>
Received request message:<?xml version="1.0" encoding="UTF-8"?><stockQuery><item>5</item></stockQuery>
Sending response message: <?xml version="1.0" encoding="utf-8"?>
<stockAvailability>
<quantity>2</quantity>
</stockAvailability>
Received response message: <?xml version="1.0" encoding="UTF-8"?>
<stockAvailability><item quantity="2"/></stockAvailability>
关联请求和响应
到目前为止,我们已经讨论了一种简单的情况,其中响应所采取的操作仅依赖于响应消息的内容。然而在很多情况下不可能如此,比如,假设将请求消息发送到总线,并通过中介对其进行传递,然后传输到 WebSphere MQ 队列管理器。在这种情况下,不可能对反向路由路径进行维护,但是对于总线正确地处理响应而言这却是必需的。另一个示例是聚合和分解消息:如果将单个消息分解为几个消息,并且所需要的是单个消息,那么在接收到所有的消息时必须确定它。
在这些情况下,需要存储关于请求消息的附加信息,以使响应中介能够看到它。这就称为关联。存储该关联信息有两种简单的方法,使得它能够在中介中进行使用。
使用数据库。
使用附加队列。
这两种机制都很常用,其中哪一个更好,则依赖于消息交互的详细信息。
使用附加队列的关联
对于存储数据而言,附加队列是一种简单、快捷并且有效的机制,并且它具有以下优点:
简化管理任务。
中介 API 为发送和接收消息提供了各种机制。
如果使用事务,将使一阶段 (one-phase ) 优化成为可能。
总线可以在数据不再有效时自动地对其进行过期处理。
使用附加队列具有以下缺点:
消息选择的性能不及数据库 SQL 选择,特别是在队列中有大量消息的时候。
当通过多个消息传递引擎将附加队列限定为集群总线 (clustered bus) 成员时,应该多加小心,必须确保关联信息位于响应中介访问的队列点。
使用数据库的关联
数据库是存储数据的功能强大的机制,它具有如下优点:
关联信息可以很容易地在应用程序之间共享。
如果存储了大量关联数据,数据库应该具有更高的伸缩性。
如果消息吞吐量高,数据库应该具有更高的伸缩性。
使用数据库具有以下缺点:
对于丢失了的响应需要某种旧数据的过期机制。
当使用事务时,需要使用两阶段 (two-phase) 提交。
与附加队列相比,需要进行更多的配置工作。
结束语
本文详述了如何使用 WebSphere Application Server V6 扩展的消息传递功能来通过中介传递请求/响应消息,并且讨论了关联请求和响应中涉及到的一些更高级的问题。
本文示例源代码或素材下载
更多精彩
赞助商链接