WebSphere Application Server V6.1 的 Web Services Feature Pack 中的 JAX-WS 客户端 API,第 2 部分: 创建代理客户端

本系列的文章将向您介绍 JAX-WS 2.0,它是受 WebSphere Application Server V6.1 的 Web Services Feature Pack 支持的新编程模型。第 2 部分将分步指导您创建一个动态代理客户端。

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 客户进行过合作。他的行业经验包括汽车、医疗保健、电信和公共事业。



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

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



2011 年 9 月 07 日

简介

此文章系列的 第 1 部分 简要介绍了 Java API for XML Web Services(后面简称为 JAX-WS)客户端 API,解释了如何使用 JAX-WS 编程模型创建一个 Dispatch 客户端。在本文中,您将了解如何创建一个动态代理客户端。本文主要内容如下:

  1. 使用可用工具生成 JAX-WS 工件
  2. 创建一个动态代理客户端
  3. 配置请求上下文
  4. 调用 SEI 操作来创建一个账户,提取现金,检查账户余额

JAX-WS 2.0 动态代理客户端

动态代理支持在运行时访问服务端点接口 (SEI),不需要静态生成 stub 类。JAX-WS 实现通过 Java™ 2 Platform, Standard Edition (J2SE) 5.0 动态代理功能来处理代理。动态代理是一种类,它实现若干接口,由 Java Runtime Environment (JRE) 在运行时生成。因此,JAX-WS 客户端没有 stub,这在 JAX-RPC 上是一个重大优势。JAX-WS 动态代理总是实现 javax.xml.ws.BindingProvider,因此,与 Dispatch 客户端一样,代理也称为 BindingProvider。

示例

对于本例,我们将通过一个简单的银行应用程序来演示使用动态代理客户端的不同方法。我们将新建一个账户,提取现金,然后查看账户信息:

下面的 WSDL 定义一个 BankingService,它只有一个 AccountsPort 端口。portType BankingSEI 定义了三个操作:createAccountwithdrawgetAccountInfo

清单 1. 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>

下面我们来看一个简单的动态代理客户端,它创建一个账户,提取现金,然后检查账户余额:

清单 2. WJAX-WS 客户端,一个动态代理示例
// Create a Dynamic Proxy client
BankingService service = new BankingService();
BankingSEI port = service.getAccountsPort();

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

// (Optional) Configure RequestContext with endpoint's URL
Map<String, Object> rc = bp.getRequestContext();
rc.put (BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
"http://localhost:9080/hello/services/BankingService”);

// Create an account with $10 in it
int accountNumber = port.createAccount("Joe Customer”, 10.00);

// Withdraw money
try{
port.withdraw(accountNumber, 1000.00);
} catch (InsufficientFundsException ife){
	InsufficientFunds if = ife.getFaultInfo();
	System.out.println("Error message: ” + if.getMessage());
}

// get account info
Holder<String> owner = new Holder<String>();
Holder<Double> balance = new Holder<Double>();

port.getAccountInfo(accountNumber, owner, balance);
System.out.println("Account number " + accountNumber);
System.out.println("Account belongs to" + owner.getValue());
System.out.println("Account balance is " + balance.getValue());

上面的示例通过动态代理 API,按照以下步骤,在一个端点上调用一个操作:

  1. 创建一个动态代理客户端。
  2. 配置请求上下文。
  3. 新建一个账户。
  4. 提取一些现金。
  5. 查看账户余额。

步骤 1:使用工具生成 JAX-WS 工件

第 1 部分 所述,动态代理客户端通过 JAXB 2.0 数据绑定技术公开 XML,从而隐藏了使用 XML API 的复杂性。这意味着,在创建账户之前,必须通过 JAX-WS 工具传递一个 WSDL 文档,以生成必要的工件。使用 Web Services Feature Pack for WebSphere Application Server V6.1 时,可以使用位于 WAS_HOME/bin/wsimport 的 wsImport 命令、wsImport Ant 任务或者 Application Server Toolkit。wsImport 基于 清单 1 中的 WSDL 生成以下工件。

下面的简单示例展示如何对 WSDL 文件使用 wsImport

<cmd>wsimport bankingservice.wsdl –d c:\app\classfiles –keep –s c:\app\javafiles

下面两个表展示了工具生成的工件。

JAX-B 架构工件 目的
CreateAccount
ResponseWithdraw
WithdrawResponse
InsufficientFunds
GetAccountInfo
GetAccountInfo
Response
XML bean,每个 bean 都代表一个 XML 元素
ObjectFactory

这个架构定义的 XML 元素和 complexTypes 的工厂。当一个 WSDL 引用多个架构时,生成的每个包都拥有自己的 ObjectFactory。

JAX-WS WSDL 工件 目的
InsufficientFundsException 一个可抛出异常,包含一个 InsufficientFunds bean
BankingSEI 一个 Java 接口,表示 portType
BankingService WSDL 服务的 Java 对等物。对于映射到这个 WSDL 服务的每个端口,这个类都包含对应的 getPort 方法。

步骤 2:创建一个动态代理客户端

上节提到过,JAX-WS 工具生成一个具体的服务实现类,这个类也称为静态服务(Static Service)。生成的这个类拥有两个公开构造函数,一个没有参数,另一个有两个参数,分别表示 WSDL 位置(java.net.URL)和服务名(javax.xml.namespace.QName)。

服务是 WSDL 服务的一种抽象表示。服务实例可以是动态服务(如 第 1 部分 所述),也可以是静态服务。本文主要关注使用静态服务。

J2SE 客户端可以通过 BankingService 的两个构造函数创建服务实例:

  • BankingService():这是创建服务的默认方法,它假定 WSDL 的位置与工件生成时的位置相同(作为本地文件或 URL)。
  • BankingService(javax.xml.namespace.QName, java.net.URL):此方法用于指向新的 WSDL 位置。要调用端点,则必须先解析这个 WSDL,以读取绑定和端点地址信息。
清单 3. 创建一个静态服务实例
// Create a static Service instance
URL wsdlLocation =
    new 
URL("http://localhost:9080/banking/services/BankingService?wsdl");
QName serviceName =
  new Qname("http://www.example.com/services/BankingService",
"BankingService");

BankingService service = new BankingService(serviceName, wsdlLocation);

BankingService 的 getAccountsPort() 方法在运行时获取一个 stub。如前所述,对于 WSDL 服务中定义的每个端口,生成的服务类都有一个对应的 getPort 方法。

清单 4. 使用静态服务创建一个代理
// Create a Dynamic Proxy client
BankingSEI port = service.getAccountsPort();

步骤 3:配置请求上下文

在与端点进行的消息交换的请求和响应阶段,JAX-WS 客户端 API 使用 BindingProvider 接口来维护独立的上下文。请求和响应上下文采用的类型是 Map<String, Object>,可通过 BindingProvidergetRequestContext 方法和 getResponseContext 方法获取请求和响应上下文。

下面是一些标准属性,可以在创建服务实例后在请求上下文上设置它们:

属性目的
javax.xml.ws.service.endpoint.address 服务端点的地址,这是特定于协议的 URI。URI 模式必须与使用的协议绑定相匹配。
javax.xml.ws.security.auth.usernamejavax.xml.ws.security.auth.password 用户名和密码,用于 HTTP 基本身份验证。
javax.xml.ws.session.maintain 表示客户端希望加入 HTTP 会话。
javax.xml.ws.soap.http.soapaction.usejavax.xml.ws.soap.http.soapaction.uri 控制是否在 SOAP-over-HTTP 请求中使用 SOAPAction HTTP 头部。

以下是特定于 WebSphere 的特性:

属性目的
com.ibm.wsspi.websvcs.Constants.READ_TIMEOUT_PROPERTY
com.ibm.wsspi.websvcs.Constants.WRITE_TIMEOUT_PROPERTY
控制发送和接收数据的超时(毫秒)。仅当通过低速连接连接到服务器或发送大量数据时修改这些属性。

下面的示例使用我们自己的地址覆盖 WSDL 中指定的端点地址:

清单 5. 设置请求上下文
// (Optional) Configure RequestContext with endpoint's URL
Map<String, Object> rc = bp.getRequestContext();
rc.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, 
"http://www.example.com:9080/banking/services/BankingService");

性能提示:您可能会感到奇怪,为何要更改地址,这是因为 Web 服务通常驻留在著名的公共位置上。通过 “?wsdl” 位置来引用 WSDL 存在以下几个问题:

  1. 当创建一个服务实例时,必须解析 WSDL 文档。
  2. 网络延迟会加剧性能下降,特别是 WSDL 及其架构比较大时。
  3. 使用公共 WSDL(比如 eBay 或 Amazon)时,WSDL 可能会通过新的操作更新,这会导致生成的 SEI 和 WSDL 之间不匹配,迫使您重新生成工件。

由于这些原因,我们建议本地存储 WSDL,总是使用上面的示例来指向实际端点。


步骤 4:调用 SEI 操作

在下面的小节中,我们将描述如何新建一个帐户,提取现金,然后检查帐户余额。

新建一个帐户

第一个示例非常简单,表示一个典型操作。我们将新建一个帐户,初始余额为 $10.00。这个服务返回新建帐户的帐户编号:

清单 6. 使用一个代理调用一个 SEI 操作
// Create an account with $10 in it
int accountNumber = port.createAccount("Joe Customer", 10.00);

您可能想知道,我们为何使用 ownerinitialBalance 参数而不是 createAccount 元素。原因是我们的 WSDL 遵循 document/literal-wrapped 模式。这个模式的特征如下:

  • 输入消息占用单独的一部分,该部分必须是一个元素。
  • 操作名与元素名相同。
  • 元素的复杂类型没有属性。

如果我们违反了上述任一条件,那么我们应该只使用简单的 “document/literal” 模式,操作签名应该如下所示:

清单 7. document/literal (non-wrapped) 操作示例
CreateAccountResponse createAccount(CreateAccount request);

提取现金

在本例中,我们将介绍一些 Web 服务故障,以及在使用代理调用 WSDL 操作时,如何将那些故障捕获为 Java 故障。为解释 Web 服务故障,我们来提取超出帐户余额的现金。大多数银行会认为这种操作是用户错误,禁止提取任何现金。

清单 1 介绍了 WSDL 故障 InsufficientFunds。这种故障是提现操作的又一个可能结果。根据 JAX-WS 规范定义的 WSDL 到 Java 映射规则,WSDL 文档的绑定部分中指定的故障名称后面带有 Exception。这样,InsufficientFunds 故障就成为 InsufficientFundsException,该故障就变成了 Java Exception

清单 8. 展示提现操作的 WSDL 片段
<schema>
	<element name=”InsufficientFunds”>
		<complexType>
			<sequence>
				<element name=”errorMessage” type=”string” />
				<element name=”errorCode” type=”int” />
			</sequence>
		</complexType>
	</element>
</schema>

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

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

<binding name="BankingSoap11Binding" type="tns:BankingSEI">

      <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>
</binding>

清单 9 所示,在余额不足 $1000 的帐户上调用这个提现操作将抛出一个 InsufficientFundsException。这个异常能够被捕获,InsufficientFunds 故障 bean 可以通过 getFaultInfo() 方法来提取。

清单 9. 提现操作抛出异常
// Withdraw money
try{
port.withdraw(accountNumber, 1000.00);
} catch (InsufficientFundsException ife){

//getFaultInfo returning InsufficientFunds bean.
	InsufficientFunds if = ife.getFaultInfo();
	System.out.println("Error message: " + if.getMessage());
}

注意,只要向服务器发送请求时出现错误,提现操作就会抛出 javax.xml.ws.WebServiceException,即使它没有明确声明会抛出异常。

检查帐户余额

现在我们检查帐户余额,看是否产生了银行费用。我们预计这个操作会返回多个值,比如帐户余额和用户姓名。由于 Java 不支持多值函数,我们将在这个操作中引入占位符。

我们来看看 getAccountInfo 操作使用的架构元素。这些元素与前面的操作中使用的那些元素非常相似,但输出元素包含两个(而不是一个)部分:

清单 10. getAccountInfo 和 getAccountInforResponse 的元素定义
<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>

我们来看看这个操作中的不同参数类型:

参数 类型 getAccountInfo 示例
wsdl:inputINaccountNumber 只在 getAccountInfo 中出现
wsdl:outputOUTownerbalance 只在 getAccountInfoResponse 中出现
wsdl:inputwsdl:outputINOUT

占位符类用于在已映射方法的签名中支持 OUTINOUT 参数。它们为那些用其他方式不可改变的对象引用提供了一个可改变的包装器。JAX-WS 定义了一个通用占位符类 javax.xml.ws.Holder<T>,可将该类用于任何 Java 类。下面是对应的 getAccountInfo 操作定义。由于我们使用了占位符,因此该方法被声明为 void

清单 11. getAccountInfo 操作
void getAccountInfo(int accountNumber, Holder<String> owner,
                                             Holder<Double> balance);

下面的代码展示如何创建占位符,调用这个操作,然后从占位符读取值:

清单 12. 从占位符检索值
// Create the Holder parameters
Holder<String> owner = new Holder<String>();
Holder<Double> balance = new Holder<Double>();

// Get account information
port.getAccountInfo(accountNumber, owner, balance);

System.out.println("Account number " + accountNumber);
System.out.println("Account belongs to" + owner.getValue());
System.out.println("Account balance is " + balance.getValue());

注意,当我们调用这个操作时,无需初始化 ownerbalance 参数。这是因为它们都是 OUT 参数,只用于提供返回值。端点将在响应我们的请求时提供这些值。


结束语

动态代理提供一种简单机制来调用 Web 服务,不需要您深入了解底层 XML。代理的使用消除了开发人员理解 Java 和 XML 之间的数据绑定的必要性,这是因为底层 JAX-WS 实现负责处理所有转换。这样,应用程序程序员就不必理解底层 XML 架构和 XML 构成 API。但是,使用代理的应用程序程序员应该理解不同的 WSDL 类型,它们是不是采用了 document/literal 或 RPC/Literal 模式,以及消息是否被包装。对 WSDL 类型的理解消除了对源和目标之间使用的是什么 SOAP 消息流的猜测,有助于理解服务器使用 SOAP 消息来解析端点操作的方式。

参考资料

学习

获得产品和技术

讨论

条评论

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=756197
ArticleTitle=WebSphere Application Server V6.1 的 Web Services Feature Pack 中的 JAX-WS 客户端 API,第 2 部分: 创建代理客户端
publish-date=09072011