使用基于 JMS 的 SOAP 传输比基于 HTTP 的 SOAP 传输更具可伸缩性、更高效;以下是一些有关如何入手的提示。
作者:Bob Murphy
2008 年 9 月发布
通过 Java 消息服务 (JMS) API 管理 SOAP 消息比通过 HTTP/S 提供更高的可伸缩性和可靠性。更高的效率源于 JMS 在一个队列中传输和存储 Web 服务请求和响应消息,直至服务器可以处理这些消息,然后客户端释放两个系统中的线程和其他资源。
配置为使用 JMS 传输的 Web 服务与其客户端通过一个 JMS 队列进行通信。客户端向该队列发送一条 SOAP 消息,并且在一个仅为 JMS 会话建立的临时列队中等待响应消息。Web 服务处理消息并将响应发回临时队列。
将基于 JMS 的 SOAP 配置为传输协议时,客户端的 Java 代码和服务不会更改。实际上,Web 服务可以同时支持基于 JMS 的 SOAP 和基于 HTTP 的 SOAP。这两个协议间的显著差异在于如何定义 Web 服务终端。
在本文中,您将学习使用基于 JMS 的 SOAP 传输的一些技巧,将 Oracle Service Bus(以前称为 BEA AquaLogic Service Bus)作为消息中枢。目前尚未对基于 JMS 的 SOAP 传输定义一个标准,如果您需要与其他供应商实施相集成,本文会有所帮助。
示例
以下示例代码显示了一个 Web 服务配置为同时支持 JMS 和 HTTP 传输。WLJmsTransport 批注配置 Web 服务中的 JMS 传输,其用法显示为粗体。除了服务 URI 和端口名之外,还定义了 JMS 队列和连接工厂的 JNDI 名称。这些项目为可选项,如果未定义,连接工厂默认为 weblogic.jms.ConnectionFactory,队列默认为 weblogic.wsse.DefaultQueue。
package example; import javax.jws.WebMethod; import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService; import javax.jws.soap.SOAPBinding; import weblogic.jws.WLHttpTransport; import weblogic.jws.WLJmsTransport; import com.accounts.AccountData; import com.accounts.accountsXMLSchema.AccountDocument; import com.accounts.accountsXMLSchema.AccountNameDocument; import com.accounts.accountsXMLSchema.AccountType; @WebService(name="AccountsPortType", serviceName="AccountsWebService", targetNamespace="http://www.accounts.com/AccountsService") @SOAPBinding(style=SOAPBinding.Style.DOCUMENT, use=SOAPBinding.Use.LITERAL, parameterStyle=SOAPBinding.ParameterStyle.WRAPPED) @WLHttpTransport( serviceUri="Accounts", portName="AccountsPort") @WLJmsTransport( serviceUri="AccountsJMS", portName="AccountsJMSPort", connectionFactory=apps.service.cf, queue="apps.service.queue") public class AccountsWebService { @WebMethod(operationName = "create") @WebResult(name="Account") public AccountDocument createAccount( @WebParam(name="AccountName") AccountNameDocument accountNameDoc) throws Exception { AccountDocument doc = AccountDocument.Factory.newInstance(); AccountType account = doc.addNewAccount(); account.setAccountName(accountNameDoc.getAccountName()); account.setAccountId(1001); return doc; } }
WSDL
Web 服务定义语言 (WSDL) 描述 Web 服务,以便客户端可以发送请求和接收响应。WSDL 的 binding 和 service 部分包含为传输协议定义的具体内容。对于基于 JMS 的 SOAP,绑定传输定义为 http://www.openuri.org/2002/04/soap/jms/,服务的终端地址包括 JMS 队列和连接工厂的 JNDI 名称;协议为 jms 而不是 http。例如,上面显示的 Web 服务的终端地址为 jms://localhost:7020/AccountsWS/AccountsJMS?URI=apps.service.queue&FACTORY=apps.service.cf。
以下几个 WSDL 片段重点关注了基于 JMS 的 SOAP 传输:
<s0:binding name="AccountsWebServiceSoapBindingjms" type="s1:AccountsPortType"> <s2:binding style="document" transport="http://www.openuri.org/2002/04/soap/jms/" /> <! ... !> </s0:binding> <s0:service name="AccountsWebService"> <s0:port binding="s1:AccountsWebServiceSoapBindingjms" name="AccountsJMSPort"> <s2:address location = "jms://localhost:7020/AccountsWS/ AccountsJMS?URI=apps.service.queue&FACTORY=apps.service.cf" /> </s0:port> </s0:service>
上面示例中的终端地址由以下部分组成:
协议............... jms
服务器地址......... localhost:7020
上下文路径...........AccountsWS
服务 URI............AccountsJMS
JMS 队列名......... apps.service.queue
JMS 连接工厂.... apps.service.cf
值得一提的是,WSDL 在使用 HTTP 的服务器上也同样可用。例如,我正使用的 WSDL 可以在 http://localhost:7020/AccountsWS/AccountsJMS?WSDL 上使用。
WebLogic JAX-RPC 客户端
独立的客户端使用 clientgen 实用程序在 WSDL 中生成的类。该实用程序创建一个 jar,包含生成 JAX-RPC 存根以与 Web 服务通信。下面是运行 clientgen 的 Ant 文件:
<project default="build-client"> <property name="weblogic.lib" value="D:/bin/bea1020/wlserver_10.0/server/lib" /> <property name="clientgen.wsdl" value="AccountsWebService.wsdl" /> <property name="clientgen.jar" value="AccountsWSClient.jar" /> <property name="clientgen.package" value="com.accounts.wsclient" /> <path id="weblogic.class.path"> <filelist dir="${weblogic.lib}"> <file name="weblogic.jar"/> </filelist> </path> <taskdef name="clientgen" classname="weblogic.wsee.tools.anttasks.ClientGenTask" classpathref="weblogic.class.path"/> <target name="build-client"> <clientgen wsdl="${clientgen.wsdl}" destFile="${clientgen.jar}" packageName="${clientgen.package}" classpathref="weblogic.class.path"/> </target> </project>
Java Web 服务客户端代码使用生成的 javax.xml.rpc.Service 类来获取生成的 Stub。和使用 HTTP Web 服务协议一样,stub 类包含代理每个 Web 服务操作的方法。在上述 Web 服务实施示例中,创建了操作方法名称并将 AccountNameDocument XML 对象作为它唯一的参数。返回对象是一个 Account XML 对象。
因为 JMS 是一个传输协议,服务器地址是可以定义的;这决定着 JNDI 队列和连接工厂对象的检索位置。此外,还可以设置一个以毫秒为单位的超时值;这决定着等待 Web 服务响应的时间。如果未设置,默认行为为永远等待。
Java Web 服务客户端如下所示。
package com.accounts.jmswsclient; import java.rmi.RemoteException; import javax.xml.rpc.ServiceException; import javax.xml.rpc.Stub; import weblogic.wsee.jaxrpc.WLStub; import weblogic.wsee.jaxrpc.WlsProperties; import com.accounts.accountsxmlschema.AccountType; public class AccountsWSClient { public static void main(String[] args) { try { AccountsWebServiceJMSService service = new AccountsWebServiceJMSService_Impl(); AccountsPortType wlstub = service.getAccountsProxyWebServiceJMSport(); ((Stub)wlstub)._setProperty( WLStub.JMS_TRANSPORT_JNDI_URL, "t3://localhost:7020"); ((Stub)wlstub)._setProperty( WlsProperties.READ_TIMEOUT, 5000); AccountType account = wlstub.create("new-account-01"); } catch (RemoteException e) { e.printStackTrace(); } catch (ServiceException e) { e.printStackTrace(); } } }
除了对 Stub.setProperty() 的两次调用,JMS 传输的 Web 服务客户端代码和 HTTP 传输的 Web 服务客户端代码都是相同的。发送一条 XML 消息并接收一个响应。
客户端无需使用 clientgen 实用程序构建生成的 Web 服务存根,而是使用 JMS 直接像队列发送一条 XML 消息。注意,XML 必须包装在 SOAP 信封中。此外,必须在 JMS 消息上设置两个特定于 WebLogic 的属性(都是字符串值)。
JMS 属性 | 值 |
_wls_mimehdrContent_Type | text/xml; charset="utf-8" |
URI | /{context-path}/{service-uri} |
在我们的示例 Web 服务中,URI 值为 /AccountsProxyWS/AccountsJMS。这个客户端还负责创建响应消息的列队和设置 JMSReplyTo 属性。
配置业务服务
Oracle Service Bus 的业务服务是用 WSDL 创建的。在我们的示例 Web 服务中,我定义了 HTTP 和 JMS 的绑定。如果您要使用此示例,确保使用 JMS 绑定或端口。
服务终端是从 WSDL 中复制的,但在 service bus 上,此终端无效,必须进行更正。在总线上,JMS 终端以 jms://{服务器:端口}/{连接工厂名}/{队列名} 形式出现。调用业务服务时,上下文路径和服务 URI 的值设置为一个传输头。
如果需要来自服务的响应,则需要一个物理响应列队。与 Web 服务客户端使用 clientgen 生成的存根不同,总线不会为回复目标创建一个临时列队。对于 Response Correlation Pattern,我使用 JMS Message Id。下面的屏幕截图显示了 Business Service Configuration 详细信息。
注意,您无法成功测试此服务。这是因为定义要调用的实际 Web 服务的 URI 未设置。另一个必需的 WebLogic 属性 _wls_mimehdrContent_Type 也没有设置。这些是在调用此业务服务的代理服务中设置的(参见下面的配置详细信息)。
配置代理服务
使用相同的 WSDL 构建一个用于公开我们的业务服务的简单代理服务,与业务服务一样,其终端地址也需要更正。还需要一个与业务服务所使用 JSM 队列不同的 JMS 队列。与业务服务不同,不需要指定响应列队,因为代理的客户端指定响应队列。
代理需要一个路由到业务服务的管道,并将传输头设置为请求操作:
只有响应传输头设置为响应 mime 类型时,客户端才能使用响应。
更新客户端以使用代理服务
配置完代理后,我们的客户端需要更新为使用新队列。在终端地址中用 WSDL 指定队列和连接工厂。可以使用 JmsTransportInfo 类改写这个值,通过将该类设置为 Stub 上的一个新属性,可以在客户端应用程序上定义一个新的终端地址。下面是设置 JmsTransportInfo 属性的 Java 代码:
String uri = "jms://localhost:7020" + "/AccountsProxyWS/AccountsJMS?URI=sb.service.queue"; ((Stub)wlstub)._setProperty("weblogic.wsee.connection.transportinfo", new weblogic.wsee.connection.transport.jms.JmsTransportInfo(uri));
结论
基于 JMS 传输协议来使用 SOAP 与基于更常见的 HTTP 协议不同。需要记住的重要内容:
- 终端地址包括队列名和连接工厂。
- 代理总线代理中需要设置 URI 和 mime 类型。
- 读取延时、队列和连接工厂都有默认值。您很可能希望覆盖这些值。