WebSphere Application Server V6.1 的 Web Services Feature Pack 中的 JAX-WS 客户端 API,第 3 部分: 使用 JAX-WS 异步编程模型

在这个关于 WebSphere Application Server V6.1 Feature Pack for Web Services 中的 JAX-WS 2.0 的文章系列的最后一部分中,了解如何创建异步 Web 客户端,以及如何使用轮询和回调模型。

Dan Sedov, 资深软件工程师, IBM

Dan Sedov 的照片Dan Sedov 是 IBM Software Group 的资深软件工程师,负责 WebSphere Application Server 的 Web 服务组件的测试工作。在过去的两年中,Dan 是负责开发和测试第一个 Web Services Feature Pack 的 WebSphere 产品团队的成员。他创建和自动化 JAX-WS Web 服务引擎的测试。



Nikhil Thaker, 资深软件工程师, IBM

Nikhil Thaker 的照片Nikhil Thaker 是 IBM Software Group 的一位顾问软件工程师,研发 Web 服务 JAX-WS 实现的 WebSphere Application Server 团队的成员。他从事 Enterprise Application Integration 工作已超过 9 年,过去 4 年一直关注 Web 服务。作为 IBM Global Services 的 Enterprise Application Integration 的一名 IT 专家,他与各种 IBM 客户进行过合作。他的行业经验包括汽车、医疗保健、电信和公共事业。



2011 年 9 月 07 日

简介

本系列 第 1 部分第 2 部分 描述 JAX-WS 客户端 API,以及如何使用 JAX-WS 编程模型来创建 Dispatch 客户端和动态代理客户端。在本文中,我们将继续讨论这些客户端 API,展示如何创建异步 Web 客户端。

本文包含以下主要内容:

  1. 介绍异步编程模型
  2. 提供轮询和回调模型
  3. 描述异步消息交换模式(Message Exchange Pattern,MEP)

JAX-WS 2.0 异步编程模型

第 2 部分 中,您了解了 JAX-WS 动态代理客户端。在本文中,您将了解异步编程模型。异步编程模型是 JAX-WS 和 JAX-RPC 之间的一个主要区别,后者只提供同步 API。

上一篇文章使用了一个简化的银行应用程序。下面是 createAccount 方法的方法声明:

int createAccount(String customerName, double initialBalance);

用异步方式调用此方法基于一个假设条件:请求可能在几秒钟内完成。但是,现代应用程序,尤其是在面向服务架构中,需要的时间可能要长得多。新开设银行账户的要求可能包括信用检查,地址验证,向各个政府部门核实情况,甚至还有一个人工工作流程。

JAX-WS 编程模型提供了两个模型,即轮询模型和回调模型,通过它们用异步方式调用操作。这两种方法都允许在等待响应时继续完成相应的处理工作。

异步服务端点接口 (Service Endpoint Interface, SEI)

清单 1 显示来自本系列 第 2 部分 的样例 WSDL 文件,本文将继续使用这个 WSDL 文件。请注意,我们并没有对其中的内容作任何更改,只是使它变成异步的。

清单 1. BankingService.wsdl WSDL 的定义
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="BankingService"
   xmlns="http://schemas.xmlsoap.org/wsdl/"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
   xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
   xmlns:tns="http://www.example.com/services/Banking"
   xmlns:types="http://www.example.com/schemas/Banking"
   targetNamespace="http://www.example.com/services/Banking">

   <types>
      <schema
		xmlns="http://www.w3.org/2001/XMLSchema"
	 	targetNamespace="http://www.example.com/schemas/Banking">

		<!--
		definition of createAccount operation request and response beans
		-->
		<element name="createAccount">
			<complexType>
				<sequence>
					<element name="owner" type="string" />
				<element name="initialBalance" type="double" />
				</sequence>
			</complexType>
		</element>

		<element name="createAccountResponse">
			<complexType>
				<sequence>
					<element name="accountNumber" type="long" />
				</sequence>
			</complexType>
		</element>


		<!--
		definition of withdraw operation request, response and fault beans 
		-->
		<element name="withdraw">
			<complexType>
				<sequence>
					<element name="accountNumber" type="long" />
					<element name="amount" type="double" />
				</sequence>
			</complexType>
		</element>

		<element name="withdrawResponse">
			<complexType>
				<sequence>
					<element name="amount" type="double" />
				</sequence>
			</complexType>
		</element>

		<element name="InsufficientFunds">
			<complexType>
				<sequence>
					<element name="errorMessage" type="string" />
					<element name="errorCode" type="int" />
				</sequence>
			</complexType>
		</element>

		<!--
		definition of getAccountInfo operation request and response beans 
		-->
		<element name="getAccountInfo">
			<complexType>
				<sequence>
					<element name="accountNumber" type="long" />
				</sequence>
			</complexType>
		</element>

		<element name="getAccountInfoResponse">
			<complexType>
				<sequence>
				  <element name="balance" type="double" />
				  <element name="owner" type="string" />
				</sequence>
			</complexType>
		</element>
	</schema>
   </types>

   <message name="createAccountRequest">
      <part name="request" element="types:createAccount"/>
   </message>

   <message name="createAccountResponse">
      <part name="response" element="types:createAccountResponse"/>
   </message>

   <message name="withdrawRequest">
      <part name="request" element="types:withdraw "/>
   </message>

   <message name="withdrawResponse">
      <part name="response" element="types:withdrawResponse"/>
   </message>

   <message name="InsufficientFunds">
      <part name="error" element="types:InsufficientFunds"/>
   </message>

   <message name="getAccountInfoRequest">
      <part name="request" element="types:getAccountInfo"/>
   </message>

   <message name="getAccountInfoResponse">
      <part name="response" element="types:getAccountInfoResponse"/>
   </message>

   <portType name="BankingSEI">
      <operation name="createAccount">
         <input message="tns:createAccountRequest"/>
         <output message="tns:createAccountResponse"/>
      </operation>

      <operation name="withdraw">
         <input message="tns:withdrawRequest"/>
         <output message="tns:withdrawResponse"/>
	   <fault message="tns:InsufficientFunds"/>
      </operation>

      <operation name="getAccountInfo">
         <input message="tns:getAccountInfoRequest"/>
         <output message="tns:getAccountInfoResponse"/>
      </operation>
   </portType>
   
   <binding name="BankingSoap11Binding" type="tns:BankingSEI">
      <soap:binding
		style="document" 
		transport="http://schemas.xmlsoap.org/soap/http"/>

      <operation name="createAccount">
         <soap:operation soapAction="createAccount"/>
         <input>	
            <soap:body use="literal"/>
         </input>
         <output>
            <soap:body use="literal"/>
         </output>
      </operation>

      <operation name=" withdraw ">
         <soap:operation soapAction="withdraw"/>
         <input>
            <soap:body use="literal"/>
         </input>
         <output>
            <soap:body use="literal"/>
         </output>
         <fault>
            <soap:fault name="InsufficientFunds" use="literal"/>
         </fault>
      </operation>

      <operation name="getAccountInfo">
         <soap:operation soapAction="getAccountInfo"/>
         <input>	
            <soap:body use="literal"/>
         </input>
         <output>
            <soap:body use="literal"/>
         </output>
      </operation>

   </binding>

   <service name="BankingService">
      <port binding="tns: BankingSoap11Binding" name="AccountsPort">
         <soap:address
             location="http://localhost:8080/banking/services
		/BankingService"/>
      </port>
   </service>
</definitions>

要使用清单 1 中的 WSDL 生成可移植工件,您可以使用 WebSphere Application Server Toolkit 或 wsimport 命令行工具等工具,它们位于 WAS_HOME/bin/wsimport 中。在本文中,我们将使用 wsimport。要使用 wsimport 生成异步工件,则需要一个单独的绑定定制文件,如清单 2 所示。

清单 2. binding.xml 绑定定制文件
<bindings 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    wsdlLocation="BankingService.wsdl"
    xmlns="http://java.sun.com/xml/ns/jaxws">    
    <bindings node="wsdl:definitions">
        <enableAsyncMapping>true</enableAsyncMapping>
    </bindings>
</bindings>

清单 2 展示了一个简单的绑定定制,它将生成 WSDL 中的所有方法的异步版本。注意,wsdlLocation 元素必须与 WSDL 文件名相匹配。输入以下命令调用 wsimport 工具:
%WAS_HOME%\bin\wsimport.bat –b binding.xml BankingService.xml

生成的 SEI 将包含为 createAccount 定义的以下操作:

清单 3. createAccount 的 SEI
// Synchronous method
int createAccount(String customerName, double initialBalance);

// Polling method
javax.xml.ws.Response<CreateAccountResponse> createAccount(String customerName, 
double initialBalance);

// Callback method
javax.concurrent.Future<?> createAccount(String customerName, double initialBalance, 
AsyncHandler<CreateAccountResponse> handler);

轮询示例

异步轮询客户端对于基于工作流实现 Web 服务很有用。在轮询客户端中,一旦发出初始请求,客户端即可自由执行其他操作,直到请求完成。这允许客户端执行其他无需依赖请求结果的任务。请求完成后,客户端即可获得响应。

表 1 描述了 javax.xml.ws.Response 对象的行为。

表 1. javax.xml.ws.Response 对象行为
cancel() 取消响应处理。这并不使服务器停止处理请求。
get() 在响应可用时检索响应。如果响应不可用,此方法阻塞。
get(long timeout, TimeUnit unit) 检索响应,但只阻塞一个特定时段。
isDone() 检查响应是否可用。
isCancelled() 检查响应处理是否被取消。
getContext() 返回与请求关联的 BindingProvider responseContext。

清单 4 展示发出一个异步轮询请求的客户端示例。

清单 4. 轮询示例
// Polling method
Response<CreateAccountResponse> response = createAccount
("Joe Customer", 10.00);

// wait for the response
while (!response.isDone()){
   // do some work that does not depend on the new account being available
   …
}

// retrieve the account number
try {
int accountNumber = response.get().getAccountNumber();
} catch (CancellationException ce) {
	// processing was cancelled via response.cancel()

} catch (ExecutionException ee} {
	// there was an error processing the request
	// getCause() returns the actual exception
	Systemout.println(ee.getCause());
}

轮询方法的一个好处是只在调用 response.get() 方法时才实际处理响应,因此,客户端能够完全控制响应被处理的时间。从这个角度讲,它与对应的同步方法非常相似。


回调示例

异步回调客户端对于实现批处理和通知使用者应用程序很有用。在回调客户端中,一旦发出请求,客户端即可自由执行其他工作;但是,响应对客户端不可用。实际响应由指定的 AsyncHandler 实现来处理。当响应对该回调处理程序可用之后,其余的代码流与轮询客户端大致相同。

表 1 描述了 javax.concurrent.Future 在异步回调客户端中的行为。

表 2. javax.concurrent.Future 在异步回调客户端中的行为
cancel() 取消响应处理。但这不会使服务器停止处理请求。
get() 客户端不应该调用这个方法,因为它的行为不确定。
get(long timeout, TimeUnit unit) 客户端不应该调用这个方法,因为它的行为不确定。
isDone() 检查响应是否可用。
isCancelled() 检查响应处理是否被取消。

清单 5 展示了调用异步回调方法的客户端。注意,这个 while 循环完全是可选的,因为实际响应(包括传输或处理请求过程中的任何失败) 都将发送给回调处理程序。

清单 5. 回调调用
// Callback method
CreateAcountHandler handler = new CreateAcountHandler();
Future>?< monitor = createAccount("Joe Customer", 10.00, handler);

// wait for the response
while (!monitor.isDone()){
   // do some work that does not depend on the new account being available
   …
}

清单 6 显示了这个异步处理程序的实现。

清单 6. AsyncHandler 实现
public class CreateAcountHandler implements AsyncHandler<CreateAccountResponse> {
	public void handleResponse(Response<CreateAccountResponse> response) {

		// retrieve the account number
		try {
		int accountNumber = response.get().getAccountNumber();
	} catch (CancellationException ce) {
		// processing was cancelled via response.cancel()

	} catch (ExecutionException ee} {
		// there was an error processing the request
		// getCause() returns the actual exception
		Systemout.println(ee.getCause());
	}
	}
}

线程考虑

回调调用不在客户端所在的线程上处理响应。这允许客户端在有很多并发异步请求时获得一定的性能控制权。要获得这种控制权,可以通过使用 BankingService.setExecutor() 方法提供一个 Executor。这些 Executor 不仅提供了一个线程池,还为 JAX-WS 运行库提供了一种方法来排列等待处理的异步响应。清单 7 展示了如何使用 Executor 工厂来创建包含 10 个线程的线程池。

清单 7. Executor 示例
BankingService svc = new BankingService();
svc.setExecutor(java.util.concurrent.Executors.newFixedThreadPool(10));

WebSphere 提供了一个默认 Executor,因此提供您自己的 Executor 是可选的。


异步 MEP

在前面的讨论中,我们只谈到了异步编程模型,在这种模型中,实际请求是同步的,且受到套接字读写超时的影响。要使这些请求在 HTTP 连接级别上变为异步的,必须在请求上下文中设置其他属性。 要支持异步消息交换,请在请求上下文中将 com.ibm.websphere.webservices.use.async.mep 设置为 true

清单 8. 实现异步消息交换
// Create a Dynamic Proxy client
BankingService service = new BankingService();
BankingSEI port = service.getAccountsPort();

// Use Proxy Instance as BindingProvider
BindingProvider bp = (BindingProvider) port;

Map<String, Object> rc = bp.getRequestContext();
rc.put("com.ibm.websphere.webservices.use.async.mep", Boolean.TRUE);

// invoke the operation asynchronously

异步消息交换实现后,会启用 WS-Addressing 支持,并将一个 wsa:ReplyTo 头添加到 SOAP 请求中。HTTP 请求可以立即了解到,原始连接已被关闭。然后,客户端会启动一个本地 HTTP 服务器,并监听来自端点的响应。

注意,这个属性是 IBM 的 JAX-WS 实现所特有的,不适用于其他供应商实现。但是,它能够与任何支持 WS-Addressing 的端点兼容,比如其他 WebSphere Application Server 或 Microsoft .NET 端点。


结束语

JAX-WS 通过轮询和回调 API 引入了一个异步编程模型。异步 Web 服务调用是非阻塞的,这意味着,应用程序可以调用一个 Web 服务,然后继续运行业务逻辑,无需等待响应。这样一个强大的编程模型(这是 JAX-PRC 中所缺乏的)的引入是客户从 JAX-RPC 迁移到新的 JAX-WS 的充分理由。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere, SOA and web services
ArticleID=756246
ArticleTitle=WebSphere Application Server V6.1 的 Web Services Feature Pack 中的 JAX-WS 客户端 API,第 3 部分: 使用 JAX-WS 异步编程模型
publish-date=09072011