本文主要介绍如何通过 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 实现的,下面来详细介绍如何实现这一示例场景。
建立工程 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 的数据转换和协议转换功能通过 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
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
从 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 次转换包括:
数据请求过程:
- 客户端传入 JMSObjectMessage 对象,JMSExport 的自定义实现类将 JMSObjectMessage
对象转换为 ESB 内部的 Business Object;
- Module Flow 的 XML Transformation 将 Business Object 转换成 Web Service
的输入对象,去调用 Service Provider 的 Web Service;
数据返回过程:
- Service Provider 返回结果数据对象,Module Flow 的 XML Transformation 将返回的对象转换成
Business Object;
- JMSExport 的自定义实现类又将 Business Object 转换成客户端的 JMSObjectMessage
对象。
由于有了 JMSExport 和 SOAPHTTPImport 以及它们不同的绑定实现(JMS Binding 和 Web Service
Binding),因此也就实现了 JMS 协议到 SOAP/HTTP 协议的转换。
学习
获得产品和技术
讨论
|