UML软件工程组织

 

 

使用 WebSphere ESB 实现协议转换和数据转换
2008-08-01 作者:李 兆全 来源: IBM
 
本文内容包括:
本文主要介绍如何通过 WebSphere ESB 实现协议转换和数据转换功能:通过 Websphere ESB 实现 SOAP/HTTP 和 JMS 之间的协议转换;实现 JMS Object Message 与 Business Object 之间、以及 Business Object 与 Java Object 的数据转换;实现客户端与 ESB 之间 request/response 的交互方式,客户端向 ESB 发送带有数据对象的请求,ESB 请求 Web 服务后,将结果以数据对象的形式返回给客户端。

引言

已有许多文章介绍了如何将 xml message 传入 ESB 进行协议转换和数据转换,而在实际的应用环境中,面向对象的设计会显得更实用些。本文主要介绍如何基于 JMS 和 WebSphere ESB 构建企业服务总线,实现 SOAP/HTTP 和 JMS 之间的协议转换;实现 JMS Object Message,Business Object 及 Java Object 之间的数据转换;以及如何实现通过 ESB 来实现 request/response 的交互方式。

示例场景

图 1:示例场景

图 1 示例展示了一个典型的端到端的 SOA 场景:

(1)Service Provider 以 SOAP/HTTP 方式发布 Web Service,该 Web Service 输入和返回都是 Java Object;

(2)客户端以 JMS 方式去调用这个 Web Service,需要经过 WehSphere ESB 进行协议转换,WehSphere ESB 将客户端的 JMS 协议转换为服务端需要的 SOAP/HTTP 协议;

(3)客户端将 JMS Object Message 发送给 WehSphere ESB,WehSphere ESB 通过 SOAP/HTTP 方式请求 Web Service。在服务请求的过程中,WehSphere ESB 将客户端传入的 JMS Object Message 转换为 Business Object,又将 Business Object 转换为服务端需要的 Java Object;在服务返回的过程中,WehSphere ESB 将服务端返回的 Java Object 转换为 Business Object,又将 Business Object 转换为客户端能识别的 JMS Object Message;

(4)客户端向 WebSphere ESB 发送请求和接收返回数据分别在两个队列上进行,客户端只需关注如何将请求的数据转化为 JMS Object Message 并放入 WehSphere ESB 的接收队列,以及如何从 WehSphere ESB 的发送队列里取出 JMS Object Message 并解析所需的结果即可。

本示例是基于 WID6.0.2 和 ESB6.0.2 实现的,下面来详细介绍如何实现这一示例场景。

创建 Service provider

建立工程 BookOrderService,来担任 Service Provider。该 service 提供了一个接口方法 order(),其输入和输出分别是 java object:OrderRequest 和 OrderResponse,将此方法发布成 Web Service,如下:

1.建立一个 Dynamic Web Project:BookOrderService

2.添加对象类、接口和实现类

对象类:

Address.java

包含属性:

private String street;

private String city;

OrderRequest.java

包含属性:

private String name;

private int count;

private Address address;

OrderResponse.java

包含属性:

private double totalPrice;

接口类:

BookOrderIf.java

public interface BookOrderIf {
public OrderResponse order(OrderRequest req);
}

实现类:

BookOrderImpl.java

实现了一个简单的业务逻辑。

public OrderResponse order(OrderRequest req) {
double totalPrice = 10.0 * req.getCount();
OrderResponse resp = new OrderResponse();
resp.setTotalPrice(totalPrice);
System.out.println("Service Provider: The book is " + req.getName()+
", the city is "+req.getAddress().getCity()+", total price is "+totalPrice);
return resp;
}

3.将 BookOrderImpl 发布为 SOAP/HTTP Web service,产生 WSDL 文件 BookOrderImpl.wsdl。

创建 WebSphere ESB

WebSphere ESB 的数据转换和协议转换功能通过 Mediation Module 工程来实现,Mediation Module 工程包含三个重要组件:Mediation Flow、Import 和 Export 组件。Mediation Flow 组件实现数据转换功能;Import 组件负责对 Service Provider 的 Web Service 进行调用;Export 组件是外界(JMS Client)访问 Mediation Module 的接口。

创建新的 Mediation Module

建立新的 Mediation Module:SOAPHTTP2JMSBinding。

建立 Mediation Module 的同时,会自动创建名称为 Mediation1 的 Mediation Flow 的组件。

Mediation Module 需要访问刚刚创建的 Service Provider,就需要导入 BookOrderImpl.wsdl 文件。从 BookOrderService Porject 里找到 BookOrderImpl.wsdl,复制到 SOAPHTTP2JMSBinding Project。WebSphere ESB 就是通过 BookOrderImpl.wsdl 来建立对 Service Provider 调用的。

添加 Import 组件

Import 组件负责完成对 Service Provider 的 Web Service 进行调用。

打开 SOAPHTTP2JMSBinding 的 Assemble Diagram,添加 Import,更名为:SOAPHTTPImport。选择 SOAPHTTPImport,添加接口“Add Interface”,选择 BookOrderImpl,这正是 BookOrderImpl.wsdl 提供的接口。

Import 组件有几种方式可以访问 Mediation Module 之外的 service provider:

Messaging Binding 方式,包括 JMS Binding、MQ Binding 和 JMS MQBinding;

Web Service Binding 方式。

因为我们刚才建立的 service provider 的接口是 SOAP/HTTP Web Service,所以 Import 组件需要选用 Web Service Binding 方式。

为 SOAPHTTPImport 添加绑定:“Generate Binding”,选择 Web Service Binding,如图 2。

图 2:Web Service Binding

在接下来的对话框中选择“Use an existing web service port”,并“Browse”选择 service ports:BookOrderImpl(因为之前已经导入了 BookOrderImpl.wsdl 文件,该文件提供了访问 service provider 的 web service port)。

新建 Business Object

Mediation Module 要进行数据对象的转换,就需要建立内部的数据对象 Business Object,来担任数据转换的中间者。

为 SOAPHTTP2JMSBinding 建立新的 Business Object:

OrderBO:

添加属性:bookname,count,city 和 street

RespBO:

添加属性:totalPrice

新建接口

建立新的 WebSphere ESB 接口,为 service provider 提供新的展现方式,以便向外界提供各种接口方式(各种 Binding 方式,见“添加 Export 组件”)。

为 SOAPHTTP2JMSBinding 新建 Interface:BookOrderIF

添加“Request Response Operation”,修改 operation name 为:order,

修改参数,如图 3:

Input

Name:orderBOType:OrderBO

OutPut

Name:respBOType:RespBO

图 3:New Interface

添加 Export 组件

为了使外界(JMS Client)访问到 Mediation Module,需要添加 Export 组件。

添加 Export,更名为 JMSExport。为 JMSExport 添加接口,“Add Interface”,选择“BookOrderIF”(注意是新建的接口,不是 BookOrderImpl.wsdl 提供的接口)。

Mediation Module 可以向外界提供几种方式的接口:

  • Messaging Binding:包括 JMS Binding、MQ Binding 和 MQ JMS Binding;
  • SCA Binding:SCA 方式提供给其他 Mediation Module 来访问;
  • Web Service Binding:提供外界 Web Service 的访问方式。

这里,我们提供 JMS 的访问方式。

为 JMSExport 添加绑定: Generate Binding,选择“JMS Binding”,如图 4。

图 4:JMS Binding

弹出对话框,JMS Export Binding 需要复杂的资源设置,如图 5:

图 5:JMS Export Binding Configuration

因为 JMS 协议要求访问者(JMS Client 和 JMSExport 都是 ESB 资源的访问者)通过 JNDI 来访问队列、队列工厂等 ESB 资源的,因此需要在 ESB 上设置各资源的 JNDI(参见 WAS 配置 ESB)。在此处需要为 JMSExport 指定各资源的 JNDI。

选择“use pre-configured messaging provider resources”,设置激活规范、接收目标和发送目标:

  • JNDI name for activation specification: jms/JMSBindingAS4Export

这个 JNDI 是 JMSExport 访问队列的激活规范(参见 WAS 配置 ESB);

  • JNDI name for receive destination: jms/JMSBingQExportRecv

这个 JNDI 指向 ESB 的接收队列(参见 WAS 配置 ESB);

  • JNDI name for send destination: jms/JMSBingQExportSend

这个 JNDI 指向 ESB 的发送队列(参见 WAS 配置 ESB);

选择 serialization type: “Serialized Business Object using JMSObjectMessage”。因为 ESB 队列接收和返回的数据都是 JMSObjectMessage 对象,因此需要选择此项。如果接收到数据是 XML,可选择“Business Object XML using JMSTextMessage”。

为了使 WebSphere ESB 能将数据返回给客户端,还需要设置 CallBack Desitination 和 Managed Connection Factory。如图 6 和图 7。

图 6:CallBack Desitination

在 JMSExport Properties 的 Binding / End-point configuration 窗口,设置:

JMS Destinations/Callback Destination Properties:jms/JMSBindingQ

图 7:Managed Connection Factory Properties

在 JMSExport Properties 的 Binding / End-point configuration 窗口,设置:

Response Connection/ Managed Connection Factory Properties: jms/JMSBindingQCF

Callback Destination 和 Managed Connection Factory 是为 JMSExport 返回数据而设置的,如果 JMS 客户端不需要返回数据,则此两项可以不设置。

创建 JMSExport 的自定义实现类

为实现 JMS 客户端的 JMSObjectMessage 对象与 Service Provider 端的纯 Java 对象进行数据转换,就需要建立 JMSExport 的自定义实现类,通过该类先实现 JMSObjectMessage 和 Business Object 的转换。

为 SOAPHTTP2JMSBinding 新建 JMSExport 自定义实现类:JMSObjectBindingImpl.java。

接口选择 com.ibm.websphere.sca.jms.data.JMSObjectBinding。此接口提供了一些数据转换的方法。

添加 JMSExport 自定义实现类的代码:

首先新建 java 类:

为 SOAPHTTP2JMSBinding 新建普通的 java 数据对象,以承载从 JMSExport 传入的 JMSObjectMessage 对象,该类也可以作为 JMS 客户端的业务对象,客户端在访问 ESB 队列时,将此业务对象与 JMSObjectMessag 对象进行转换(详见客户端 Servlet 和 ServiceConsumer 的代码):

JMSDataObject.java

实现接口:java.io.Serializable

添加以下属性到该类:

private String bookname;

private int count;

private String city;

private String street;

private double totalPrice;

打开实现类 JMSObjectBindingImpl,添加下列代码:

public class JMSObjectBindingImpl implements JMSObjectBinding {
private JMSDataObject jmsdata;
private DataObject bo;

public void setObject(Object arg0) throws DataBindingException {
System.out.println("set JMS Object."); 
jmsdata = (JMSDataObject)arg0;
}
public Object getObject() throws DataBindingException {
return jmsdata;
}
public boolean isObjectType() {
return false;
}
public void setObjectType(boolean arg0) {
}
public int getMessageType() {
return JMSObjectDataBinding.OBJECT_MESSAGE;
}
public void read(Message arg0) throws JMSException {
System.out.println("read JMS Message..."); 
jmsdata = (JMSDataObject)((ObjectMessage)arg0).getObject();
com.ibm.websphere.sca.ServiceManager serviceManager =
new com.ibm.websphere.sca.ServiceManager();
com.ibm.websphere.bo.BOFactory factory = (com.ibm.websphere.bo.BOFactory)
serviceManager.locateService("com/ibm/websphere/bo/BOFactory");
DataObject orderBO = factory.create("http://SOAPHTTP2JMSBinding","OrderBO");
orderBO.setString("bookname",jmsdata.getBookname());
orderBO.setString("city",jmsdata.getCity());
orderBO.setString("street",jmsdata.getStreet());
orderBO.setInt("count",jmsdata.getCount());
try {
setDataObject(orderBO);
} catch (DataBindingException e) {
throw new JMSException(e.getLocalizedMessage());
}
}
public void write(Message arg0) throws JMSException {
System.out.println("write JMS Message..."); 
try {
DataObject bo = getDataObject();
JMSDataObject jmsobj = new JMSDataObject();
jmsobj.setTotalPrice(bo.getDouble("totalPrice"));
((ObjectMessage) arg0).setObject(jmsobj);
} catch (DataBindingException e) {
throw new JMSException(e.getLocalizedMessage());
}
}
public boolean isBusinessException() {
return false;
}
public void setBusinessException(boolean arg0) {
}
public DataObject getDataObject() throws DataBindingException {
return bo;
}
public void setDataObject(DataObject arg0) throws DataBindingException {
System.out.println("set Service Data Object."); 
bo = arg0;
}

该类实现了以下功能:

当客户端向 JMSExport 发出请求时,该类负责将外部传入的 JMSObjectMessage 转变为内部的 Business Object,转变过程依次经过以下方法:

(1)read(message)

(2)getDataObject()

当 JMSExport 向客户端返回数据时,该类负责将内部的 Business Object 转变为外部的 JMS Object Message,转变过程依次经过以下方法:

(1)setDataObject()

(2)getMessageType()

(3)write(message)

设置 JMSExport 的自定义实现类

将 JMSExport 的 Method Bindings 设置为自定义的实现类:JMSObjectBindingImpl。

返回 Assembly Diagram,选择 JMSExport,选择 Method bindings,如图 8:

图 8:Method Bindings

选择“Show Advanced”,设置以下属性,选择刚刚定义的实现类。如图 9:

Input data binding format:

Serialization type: User Supplied

Input data binding class name: order.JMSObjectBindingImpl

Output data binding format:

Serialization type: User Supplied

Input data binding class name: order.JMSObjectBindingImpl

图 9:Data Binding Format

连接 Export,Mediation 和 Import

选择 JMSExport 的 Add Wire,拖拽到 Mediation1;选择 Mediation1 的 Add Wire,拖拽到 SOAPHTTPImport 上。将 Export,Mediation 和 Import 连接起来,如图 10。

图 10:Add Wire

配置 Mediation Flow,做数据对象转换

因为 Service Provider 的 service 有输入对象和返回对象,因此需要作输入对象和返回对象分别做转换:

一是 Business Object 到 Service Provider 的输入对象的转换;

二是 Service Provider 的返回对象与 Business Object 的转换。

从 Mediation1 的菜单里选择“Generate Implementation”,进入 Mediation Flow Editor 窗口,连接两个 order 方法,如图 11:

图 11:Mediation Flow

先为请求的数据对象作 mapping:

在“Request:order”的窗口里,添加 XML Transformation,命名为 XML Transformation1,并将 XML Transformation1、order:BookOrderIF 和 order:BookOrderImpl 连接起来,如图 12:

图 12:XML Transformation

创建 Mapping file。选择 XML Transformation1,选择 Properties 的 Details,如图 13:

图 13:Create Mapping File

选择 New…,跳过 New XSLT Mapping 窗口,显示 Mapping 窗口,展开所有属性,将属性一一做 mapping。这样,通过 XSLT Mapping,就将 Business Object 转换为 Service Provider 的输入对象,如图 14:

图 14:Data Mapping 1

同样,为返回的数据对象作 Mapping。

在“Response:order”窗口,添加 XML Transformation,命名为 XML Transformation2。将 Service Provider 的返回数据对象和 Business Object 属性一一作 Mapping。如图 15:

图 15:Data Mapping 2

WAS 配置 ESB

Mediation Module 提供给客户端 JMS 的访问方式。因此需要在 ESB 上建立队列以及 JMS 的 JNDI 访问对象,以实现客户端和 ESB 的 JMS 访问。

建立总线目标

打开 admin console,选择服务集成 -> 总线,选择“SCA.SYSTEM.widCell.Bus”。

新建队列目标,建立以下三个队列目标:

  • JMSBingQ
  • JMSBingQExportRecv
  • JMSBingQExportSend

建立 JMS 队列

选择资源 ->JMS 提供程序 -> 缺省消息传递。

新建 JMS 队列,建立下面三个 JMS 队列:

(1)JMSBindingQ

JNDI 名称:jms/JMSBindingQ

队列名:JMSBingQ

总线名:SCA.SYSTEM.widCell.Bus

(2)JMSBingQExportRecv

JNDI 名称:jms/ JMSBingQExportRecv

队列名:JMSBingQExportRecv

总线名:SCA.SYSTEM.widCell.Bus

(3)JMSBingQExportSend

JNDI 名称:jms/ JMSBingQExportSend

队列名:JMSBingQExportSend

总线名:SCA.SYSTEM.widCell.Bus

建立 JMS 队列连接工厂

选择资源 ->JMS 提供程序 -> 缺省消息传递

新建 JMS 队列连接工厂:

JMSBindingQCF

JNDI 名称:jms/JMSBindingQCF

总线名:SCA.SYSTEM.widCell.Bus

建立激活规范

选择资源 ->JMS 提供程序 -> 缺省消息传递

新建“JMS 激活规范”:

JMSBindingAS4Export

JNDI 名称:jms/JMSBindingAS4Export

目标类型:队列

目标 JNDI 名称:jms/JMSBingQExportRecv

总线名:SCA.SYSTEM.widCell.Bus

创建 Service Consumer

从 Business Integration Perspective 切换到 J2EE Perspective。创建客户端的访问程序。

创建 web 工程 ServiceConsumerWeb

创建新的 Web 工程 ServiceConsumerWeb,EAR project 为 ServiceConsumerWebEAR。

客户端将使用 JMSDataObject.java 类作为其业务对象类(见“创建 JMSExport 的自定义实现类”)。因此,需要将 SOAPHTTP2JMSBinding 工程 export 成 SOAPHTTP2JMSBinding.jar 包,并将 jar 包输出到 ServiceConsumerWebEAR\ 目录下。

并且设置 ServiceConsumerWeb 的属性:

将 SOAPHTTP2JMSBinding.jar 加入到“java Build Path/Libraries”;

选择 java JAR Dependencies,选择 SOAPHTTP2JMSBinding.jar。

创建 ServiceConsumer 类

为 ServiceConsumerWeb 工程新建 java 类:ServiceConsumer,实现客户端请求的逻辑。

public class ServiceConsumer {
private String QCF ="jms/JMSBindingQCF";
private String jndi_recvfrom_esb = "jms/JMSBingQExportSend";
private String jndi_sendto_esb ="jms/JMSBingQExportRecv";

public String sendMes(Object mes) {//throws ISSXException {
QueueConnectionFactory qcf = null;
QueueConnection connection = null;
QueueSession session = null;
QueueSender sender = null;
try {
InitialContext ic = new InitialContext();
qcf = (QueueConnectionFactory) ic.lookup(QCF);
// Create a connection
connection = qcf.createQueueConnection();
connection.start();
session = connection.createQueueSession(false,Session.AUTO_ACKNOWLEDGE);
Queue outQueue = (Queue) ic.lookup(jndi_sendto_esb);
// Create a QueueSender
sender = session.createSender(outQueue);
sender.setTimeToLive(0);
Message message = null;
if(mes instanceof Object){
System.out.println("Service consumer: send request to ESB.");
message = (ObjectMessage)session.createObjectMessage((JMSDataObject)mes);
}
message.setStringProperty("TargetFunctionName","order");
message.setJMSType("order");
sender.send(message);
// Close the connection
sender.close();
session.close();
connection.close();
sender = null;
session = null;
connection = null;
} catch (JMSException je) {
je.printStackTrace();
Exception le = je.getLinkedException();
if (le != null){
le.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
return "OK";
}
public String receiveMes(){
QueueConnectionFactory qcf = null;
QueueConnection connection = null;
QueueSession session = null;
QueueReceiver receiver = null;
String ret = null;
try {
InitialContext ic = new InitialContext();
qcf = (QueueConnectionFactory) ic.lookup(QCF);
// Create a connection
connection = qcf.createQueueConnection();
connection.start();
sessio = connection.createQueueSession(true,Session.AUTO_ACKNOWLEDGE);
// Obtain the Destination object from JNDI
Queue inQueue = (Queue) ic.lookup(jndi_recvfrom_esb);
// Create a QueueSender
receiver = session.createReceiver(inQueue);
Message message = receiver.receive(5000);
if(message instanceof ObjectMessage){
ObjectMessage omes = (ObjectMessage)message;
JMSDataObject obj = (JMSDataObject)omes.getObject();
System.out.println("Service consumer: get response from ESB.TotalPrice="
+obj.getTotalPrice());
}else{
System.out.println("Service consumer: receive other message.");
}
session.commit();
receiver.close();
session.close();
connection.close();
receiver = null;
session = null;
connection = null;
} catch (JMSException je) {//Log,save,sendmail,throw exception
je.printStackTrace();
Exception le = je.getLinkedException();
if (le != null){
le.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
return ret;
}
}

ServiceConsumer 类主要实现两个方法:

sendMes(Object)方法,将传入的业务数据对象转化为 JMSObjectMessage,发送到 ESB 的接收队列里;

receiveMes()方法,将结果从 ESB 的发送队列里取出,然后将 JMSObjectMessage 解析为业务数据对象。

创建测试的 servlet

为 ServiceConsumerWeb 工程创建 servlet:TestServlet

protected void doGet(HttpServletRequest arg0, HttpServletResponse arg1)
throws ServletException, IOException {
ServiceConsumer consumer = new ServiceConsumer();
JMSDataObject data = new JMSDataObject();
data.setBookname("abc");
data.setCount(10);
data.setCity("beijing");
data.setStreet("zhongguancun");
consumer.sendMes(data);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
consumer.receiveMes();
}

Servlet 主要实现的功能是:

建立业务对象类,调用 ServiceConsumer 的发送请求和接收返回的方法。

测试

将 BookOrderService、SOAPHTTP2JMSBinding 和 ServiceConsumerWeb 三个工程发布运行。

在 IE 浏览器地址栏输入以下地址:

http://localhost:9080/ServiceConsumerWeb/TestServlet

测试结果,控制台输出以下结果:

(1)SystemOut O Service consumer: send request to ESB.

(2)SystemOut O ESB: read JMS Message.

(3)SystemOut O Service Provider: The book is abc, the city is beijing, total price is 100.0

(4)SystemOut O ESB: write JMS Message.

(5)SystemOut O Service consumer: get response from ESB. TotalPrice=100.0

结果分析:

(1)客户端向 ESB 发出请求,将 JMS Object Message 发送给 ESB;

(2)ESB 读取 JMS Object Message,转化为 ESB 内部的 Business Object;ESB 将 Business Object 通过 XML Transformation1 转化为 BookOrderService 所需要的数据对象,并向 BookOrderService 发出请求;

(3)BookOrderService 得到请求,进行处理,向 ESB 返回数据对象;

(4)ESB 获得 BookOrderService 的返回对象,通过 XML Transformation2 转化为内部的 Business Object,然后再转化为 JMS Object Message,返回给客户端;

(5)客户端得到 ESB 的返回的 JMS Object Message,解析到 BookOrderService 返回的结果。

总结

从整个过程来看,WebSphere ESB 在数据转换过程实现了两种数据类型的 4 次转换,这两种数据类型分别是 Service Consumer 端传入的 JMSObjectMessage 对象和 Service Provider 端的 Web Service 输入对象和返回对象。在 ESB 内部,JMSExport 的自定义实现类和 Module Flow 的 XML Transformation 承担了数据转换的任务。4 次转换包括:

数据请求过程:

  1. 客户端传入 JMSObjectMessage 对象,JMSExport 的自定义实现类将 JMSObjectMessage 对象转换为 ESB 内部的 Business Object;
  2. Module Flow 的 XML Transformation 将 Business Object 转换成 Web Service 的输入对象,去调用 Service Provider 的 Web Service;

数据返回过程:

  1. Service Provider 返回结果数据对象,Module Flow 的 XML Transformation 将返回的对象转换成 Business Object;
  2. JMSExport 的自定义实现类又将 Business Object 转换成客户端的 JMSObjectMessage 对象。

由于有了 JMSExport 和 SOAPHTTPImport 以及它们不同的绑定实现(JMS Binding 和 Web Service Binding),因此也就实现了 JMS 协议到 SOAP/HTTP 协议的转换。

参考资料

学习 获得产品和技术 讨论
 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号