级别: 中级 李 传峰 (lichuanf@cn.ibm.com), IBM中国软件研发实验室SOA设计中心工程师
2007 年 4 月 30 日 本文重点阐述如何基于 WESB 实现 SOAP 和 JMS 消息头的转换,以及如何在 WESB 中处理 SOAP 和 JMS 消息头。
概述
作为一种开放架构,SOA(Service Oriented Architecture)可以支持多种协议和消息格式。通过ESB(Enterprise Service Bus)集成不同协议和消息格式的Service,将各种消息格式转换成通用对象模型(Common Object Model),屏蔽消息间的差异。
IBM的WESB(WebSphere ESB)产品支持多种绑定方式,如Web service绑定、JMS绑定等。通过这些绑定,WESB将外部消息统一转换为SDO(Service Data Object)。IBM developerWork上有不少关于WESB上Web service和JMS绑定的文章,对怎样处理SOAP和JMS消息体有很充分的阐述,这里不再赘述。
本文重点阐述如何基于WESB实现SOAP和JMS消息头的转换,以及如何在WESB中处理SOAP和JMS消息头。
由于WPS(WebSphere Process Server)与WESB完全兼容,本文基于WPS实现,版本为6.0.1.3。开发工具为WID(WebSphere Integration Developer) V6.0.1.2。
关于消息头
对于网络上传输的消息,其承载的内容常常分为消息头和消息体,如相对底层的IP、TCP、UDP消息,以及处于应用层的HTTP消息。业务相关内容存入消息体中,消息头中包含与业务无关的管理信息,比如消息的优先级、序列号、地址信息等。
SOA中的SOAP和JMS消息同样也包含消息头和消息体。
SOAP消息头
SOAP消息头的一个典型应用是传送与安全相关的信息,如消息的数字签名,身份认证的令牌(Token)等,具体可以参考WS Security系列规范。另外还有很多Web service规范,如WS Addressing、WS Policy,也需要扩展SOAP消息头实现。
SOAP消息头不仅可以传送Web service规范中定义的元素,也可以传送用户自定义的元素。SOAP消息头中的元素遵循XML格式。
JMS消息头
JMS消息头中可以包含一些预先定义的标准属性,如JMSDestination、JMSMessageID、JMSPriority等。JMS消息头中也可以包含用户自定义的属性,自定义属性采用“属性名,属性类型,属性值”的三元组格式。
相对于SOAP消息头的灵活的XML格式,JMS消息头的格式更加严格。
消息头转换的应用场景
为了提高效率,某企业需要将一个内部的XML over JMS Service包装成Web Service,使合作伙伴和客户也可以从外部调用。JMS Service可以区分服务请求的优先级,该企业要求包装后的Web Service也支持区分服务水平的能力。
JMS Service通过JMS消息头中的JMSPriority属性区分服务请求的优先级。相应地,在Web service的SOAP消息头中定义标识优先级的元素。
图1 系统架构图
如上图所示,在WESB中,通过Web service绑定和JMS绑定,转换SOAP和JMS协议和消息格式,同时通过消息中介转换SOAP和JMS消息头中的优先级相关属性。
后边将逐步构建这个场景。JMS Service由一个消息驱动Bean(Message Driven Bean)实现,将收到的JMS消息体原样返回。在WESB的客户化消息中介中实现SOAP消息头到JMS消息头的转换。外部用户通过Web客户端访问暴露出来的Web service。
下载区中包含了Web客户端、中介模块和消息驱动Bean的项目代码。
创建客户化消息中介的框架
WESB不仅支持预定义的消息中介模式,如XSL Transformation、Message Filter、Message Logger、Database Lookup等,还支持用户自定义的消息中介(Custom Mediation)。用户可以在客户化消息中介中编写Java代码,灵活处理输入消息对象,构造用户需要的输出消息对象。
本文将在客户化消息中介中实现SOAP和JMS 消息头的转换。以下简述创建客户化消息中介的关键步骤。可以参考下载区中已经完成的客户化消息中介HeaderMedModule.zip。
1. 创建中介模块
图2 创建中介模块
1)创建一个中介模块,其中包含两个Business Object类型:BodyType和HeaderType。HeaderType中有一个名为“priority”的整型元素,标识消息的优先级。
2)创建一个接口,输入、输出参数类型都是BodyType,接口名称为HeaderMedInterface。如上图所示。
2. 创建中介流程
1)创建一个中介流程,源接口和目标接口都是HeaderMedInterface,流程名称为HeaderMedFlow。
图3 创建中介流程
2)如上图所示,在Request Flow中创建客户化中介原语CusMediation。
图4 定义客户化中介原语
3)定义客户化中介原语。注意不能用缺省的“/body”作为Message Root,因为“/body”只能处理消息体,不能处理消息头。这里选取“/”作为Message Root,可以处理整个消息,如上图所示。定义原语的过程中,需要创建一个新的接口,接口名称为CusMedInterface,并自动生成Java实现sca.component.mediation.java.impl.CusMedInterfacePartner。
3. 组装集成应用
图5 组装集成应用
1)创建一个中介组件,其实现为上边创建的中介流程HeaderMedFlow。该中介组件有两个引用,HeaderMedInterfacePartner和CusMedInterfacePartner。
2)为中介组件创建一个Export,选取soap/http的Web service绑定。生成的WSDL文件的缺省名称为HeaderMedFlowExport_HeaderMedInterfaceHttp_Service.wsdl。
图6 创建Import
3)创建一个Import,连接到引用HeaderMedInterfacePartner。Import选取JMS绑定类型。如上图所示,输入JMS资源的JNDI名称,序列化类型选取“Business Object XML using JMS TextMessage”,不启用“JMS Function Selector”。
4)创建一个Java组件,连接到引用CusMedInterfacePartner,其Java实现为定义客户化中介原语过程中生成的sca.component.mediation.java.impl.CusMedInterfacePartner。
编写客户化消息中介的代码
对于定义客户化中介原语过程中自动生成的Java实现类sca.component.mediation.java.impl.CusMedInterfacePartner,需要在其mediate()方法中需要填入客户化消息中介的处理逻辑。
/**
* Method generated to support implemention of operation "mediate" defined for WSDL port
* type named "interface.CusMedInterface".
*
* The presence of commonj.sdo.DataObject as the return type and/or as a parameter
* type conveys that its a complex type. Please refer to the WSDL Definition for more
* information on the type of input, output and fault(s).
*/
public DataObject mediate(DataObject input1) {
// To override the generated Java Snippet, please comment out the following method call
sca.component.mediation.java.impl.CusMedInterfacePartnerCustomLogic.JavaSnippet snippet
= new sca.component.mediation.java.impl.CusMedInterfacePartnerCustomLogic
.JavaSnippet();
return snippet.execute(input1);
}
|
自动生成的mediate()方法进一步调用sca.component.mediation.java.impl.CusMedInterfacePartnerCustomLogic的execute ()方法。在execute ()方法中编写以下代码。
/**
* The following method is required for Java snippet support. Do not remove.
* @generated (com.ibm.wbit.java)
*/
public DataObject execute(DataObject input1) {
// covert input1 to ServiceMessageObject type
ServiceMessageObject smo = (ServiceMessageObject) input1;
// get headers in the SMO
HeadersType headers = smo.getHeaders();
// get SOAP header element
if (headers.getSOAPHeader().size() > 0) {
SOAPHeaderType soapHeader = (SOAPHeaderType) headers.getSOAPHeader().get(0);
DataObject soapHeaderDO = (DataObject) soapHeader.getValue();
System.out.println(OutputHelper.print(soapHeaderDO));
// get priority
int priority = soapHeaderDO.getInt("priority");
System.out.println("Priority in SOAP Header: " + priority);
// create a new JMSHeaderType
JMSHeaderType jmsHeader = ServiceMessageObjectFactory.eINSTANCE.
createJMSHeaderType();
jmsHeader.setJMSPriority(BigInteger.valueOf(priority));
// set JMS header element
headers.setJMSHeader(jmsHeader);
// delete SOAP header
smo.getHeaders().getSOAPHeader().remove(0);
}
return (DataObject) smo;
}
|
还记得在创建客户化消息中介的框架时,选取消息类型的Message Root为“/”,这决定了客户化消息中介的具体输入类型为ServiceMessageObject(SMO)。
SMO中的SOAP消息头是一个SDO的列表,其中每个SDO的结构由XML Schema定义。本文示例中的SDO列表中只有一个SDO,类型为上边创建的HeaderType。
SMO中的JMS消息头为JMSHeaderType类型的SDO,支持采用直接的方法访问SDO中的元素,这里调用了setJMSPriority()方法。
到现在为止,客户化消息中介已经构建完成,下边将构建用于测试的客户端和JMS Service。
构建测试客户端和JMS Service
1. 构建JMS Service
请参考下载区中已经完成的消息驱动Bean HeaderMedService.ear。
JMS Service实现为一个消息驱动Bean,其onMessage()方法的代码如下。
public void onMessage(javax.jms.Message msg) {
try {
System.out.println("Enter MDB ...");
// print the value of JMSPriority
System.out.println("JMSPriority is: " + msg.getJMSPriority());
// get payload of the JMS message
String payload = ((TextMessage) msg).getText();
// get message id of the JMS message
String MessageID = msg.getJMSMessageID();
// get JMS resources to return the response message
InitialContext context = new InitialContext();
Queue q = (Queue) context.lookup("java:comp/env/test/sender");
QueueConnectionFactory qcf = (QueueConnectionFactory) context.
lookup("java:comp/env/test/QCF");
QueueConnection conn = qcf.createQueueConnection();
conn.start();
QueueSession session = conn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
QueueSender sender = session.createSender(q);
// create the response message with the same payload as the request message
TextMessage sendMsg = session.createTextMessage(payload);
// set JMSCorrelationID of the response message to correlate with the request message
sendMsg.setJMSCorrelationID(MessageID);
// send the response message
sender.send(sendMsg);
// release the JMS resources
sender.close();
session.close();
conn.close();
System.out.println("Exit MDB ...");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
|
该消息驱动Bean首先打印出JMSPriority的值,然后保持消息体不变,构建并返回响应的JMS消息。构建响应消息时,需要将其JMSCorrelationID设置为申请消息的JMSMessageID,以配对申请消息和响应消息。
2. 构建客户端
请参考下载区中已经完成的Web客户端HeaderMedClientEAR.ear。
外部用户通过Web访问暴露的Web service。这里使用JAX-RPC Handler生成SOAP消息头,代码如下。
public boolean handleRequest(MessageContext context) {
// Fill in method body or delete method to use GenericHandler
System.out.println("handler ...");
SOAPMessageContext soapContext = (SOAPMessageContext) context;
try {
SOAPHeader header = soapContext.getMessage().getSOAPPart()
.getEnvelope().getHeader();
// add HeaderType element
SOAPElement header1 = header.addChildElement("HeaderType", "header",
"http://HeaderMedModule");
// add priority element
SOAPElement header2 = header1.addChildElement("priority");
// add value of the priority
header2.addTextNode("2");
} catch (SOAPException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return true;
}
|
JAX-RPC Handler为SOAP申请消息添加SOAP消息头,消息头的格式符合上边创建的HeaderType的类型定义。
测试应用场景
图7 测试客户端
Web service客户端发送的SOAP申请消息如下。
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<header:HeaderType xmlns:header="http://HeaderMedModule">
<priority>2</priority>
</header:HeaderType>
</soapenv:Header>
<soapenv:Body>
<p448:operation1 xmlns:p448="http://HeaderMedModule/HeaderMedInterface">
<InputPara>
<payload>aaa</payload>
</InputPara>
</p448:operation1>
</soapenv:Body>
</soapenv:Envelope>
|
可以看到,SOAP申请消息中包含了类型为HeaderType的消息头,标识消息优先级为2。
SOAP消息经过中介模块后,转换为JMS消息到达MDB,系统输出如下。
[07-1-22 17:39:24:906 CST] 00000069 SystemOut O (DataObject: HeaderType) {
priority=(Data)'2'
}
[07-1-22 17:39:24:906 CST] 00000069 SystemOut O Priority in SOAP Header: 2
[07-1-22 17:39:25:000 CST] 00000096 SystemOut O Enter MDB ...
[07-1-22 17:39:25:000 CST] 00000096 SystemOut O JMSPriority is: 2
|
首先,SOAP消息头经过Web service绑定的Export转换为SDO,然后,在客户化消息中介中取出priority并构建JMS消息头对应的SDO,并通过JMS绑定的Import转换为JMS消息头。
总结
本文通过一步步的构建一个模拟的应用场景,展示怎样基于WESB/WPS处理和转换SOAP/JMS消息头。消息头常被用来实现非功能性需求,这种场合下,本文具有更加直接的参考意义。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| Web测试客户端 | HeaderMedClientEAR.ear | 37KB | HTTP |
|---|
| 消息中介样例 | HeaderMedModule.zip | 34KB | HTTP |
|---|
| 测试消息驱动Bean | HeaderMedServiceEAR.ear | 5KB | HTTP |
|---|
参考资料
关于作者  | |  | 李传峰:IBM中国软件研发实验室SOA设计中心工程师,多年IT企业工作经验,对软件开发及应用有浓厚兴趣。目前正在从事ESB和SCA的实现工作。联系方式:lichuanf@cn.ibm.com
|
对本文的评价
|