级别: 初级 Ping Wang, 软件工程师, IBM Russell Butek (butek@us.ibm.com), 软件工程师, IBM Software Group
2004 年 2 月 01 日 在 WSDL 操作中显式地声明错误,就像在 Java 方法中显式地声明错误一样,是良好的编程实践。本文首先研究在没有 wsdl:fault 时的异常行为。然后它着重介绍了 wsdl:fault 如何被映射到已检查 Java 异常(checked Java exception)以及 JAX-RPC 运行时是如何处理这个已检查异常的。
在 SOAP Web 服务的世界中,错误是以 SOAP Fault
的形式从服务器传送到客户端的。SOAP Fault 由 faultcode、faultstring 以及可选的 faultactor 和 detail 所组成。JAX-RPC 规范定义了关于如何从一个 Java 异常映射到一个 SOAP
Fault(服务器端)以及如何从 SOAP Fault 再向回映射到 Java 异常(客户端)的各种规则。
从服务器中可以抛出四种类型的异常。
-
java.rmi.RemoteException
-
java.lang.RuntimeException
-
javax.xml.rpc.soap.SOAPFaultException (一种特殊的
RuntimeException 子类)
- 已检查、用户定义的异常(从 WSDL 的
wsdl:fault
构造中映射)
客户端将接收到下列异常类型之一。注意,客户端不能捕获除
SOAPFaultException 以外的任何
RuntimeException 。
-
java.rmi.RemoteException
-
javax.xml.rpc.soap.SOAPFaultException
- 已检查的、用户定义的异常
本文首先讨论了当服务器抛出不同的异常时客户端的预期行为,然后强调了已检查的异常的使用。
RemoteException
JAX-RPC 要求,在服务端点接口(service endpoint interface,SEI)上的所有远程方法都抛出标准的
java.rmi.RemoteException 。这就使由于通信或者运行时的困难引起的异常能够传送回调用者。然而,却没有用于发送
RemoteException
的特殊子类的可移植方法。
应用程序本身同样也可以抛出
RemoteException 。然而,因为没有发送特殊的
RemoteException
的可移植方法,所以客户不能捕获特殊的
RemoteException 。对于一个从服务器返回的特定 SOAP
Fault,不同的客户端的 JAX-RPC 运行时可能有不同的解释,并且产生不同的
RemoteException 。由于这个互操作性的问题,应用程序应该避免抛出
RemoteException 。
RuntimeException
当在服务器端 JAX-RPC runtime
发生了问题,导致抛出
RuntimeException (例如,NullPointerException)时,该异常将传送回客户,但是它将被作为 SOAP
Fault 来传送。客户端运行时将 SOAP Fault 映射到
RemoteException
或
SOAPFaultException (将在下面描述)。因此,服务端点不应该抛出
RuntimeException ,期望客户端总会捕获
RuntimeException ,因为客户端可能接收到
RemoteException
来代替。
SOAPFaultException
有一个特殊的
RuntimeException :javax.xml.rpc.soap.SOAPFaultException。
SOAPFaultException 比
RuntimeException
更具描述性,它规定了传送到客户端的确切 SOAP Fault 消息。换句话说,究竟是谁,是运行时还是应用程序,控制着 SOAP
Fault 响应。因此,如何将 SOAP fault 映射到合适的异常实际上依赖于
SOAPFaultException 的上下文,它可能被映射到
SOAPFaultException 、
RemoteException
或者甚至是已检查的用户异常。
SOAPFaultException
通常由 JAX-RPC 处理器使用。通常,JAX-RPC 应用程序本身应该避免抛出
SOAPFaultException 。
已检查的用户异常
良好的编程实践通常包括显式地定义已检查用户异常作为接口契约的一部分。在 JAX-RPC
世界中,程序员需要首先定义
wsdl:fault 作为
wsdl:operation 的一部分。
wsdl:operation
允许多个
wsdl:fault 元素,正如 Java
方法允许多个异常一样。每个
wsdl:fault
都被映射到一个用户异常作为 SEI 的一部分。在大部分情况下,Java 异常没有复杂的数据结构;
wsdl:fault 情况也类似,
wsdl:fault 所引用的
Schema
定义通常都是简单易懂的。然而,仍然非常重要的是,程序员要仔细考虑希望抛出哪种类型的异常,然后再定义合适的
wsdl:fault 。
映射规则
与
wsdl:input 和
wsdl:output 不同,
wsdl:fault
引用的消息只允许有一个单一的消息部分,这个部分可以引用一个简单的类型或一个复杂的类型。如果这个部分元素具有一个类型属性,那么您就可以直接看出这个类型是简单的(例如,xsd:int、xsd:string,等等)还是复杂的。如果这个部分元素具有一个元素属性,那么您就不得不进入到元素中去查看类型是简单的或是复杂的。
用于简单类型的映射规则
对于简单的类型,由
wsdl:message 元素的属性名称来映射 Java 异常名称。
wsdl:part
名称映射到 getter method 和 Java 异常的构造器中的参数。例如,在
清单 1
中 WSDL 的错误消息映射到了在
清单 2
中的 Java 语言异常。
清单 1. 带有一个简单错误的 WSDL 定义
<definitions ...>
<message name="empty"/>
<message name="InsufficientFundsFault">
<part name="balance" type="xsd:int"/>
</message>
<portType name="Bank">
<operation name="throwException">
<input message="tns:empty"/>
<output message="tns:empty"/>
<fault name="fault" message="tns:InsufficientFundFault"/>
</operation>
</portType>
...
</definitions>
|
清单 2. 来自清单 1
中的错误的 Java 异常
public class InsufficientFundFault extends java.lang.Exception {
private int balance;
public int getBalance() {
return this.balance;
}
public InsufficientFundFault() {
}
public InsufficientFundFault(int balance) {
this.balance = balance;
}
} |
用于复杂类型的映射规则
对于 complexType,由 complexType 的名称(或者元素的名称,如果错误信息的部分引用了元素的话)来映射 Java 异常名称。complexType 内部的每个元素都被映射到 Java 异常的构造器中的参数和 getter
方法。注意,与 Bean 不同,这里没有 setter 方法。设置这样一个字段的惟一方法就是通过异常构造器(exception constructor)。例如,
清单 3
中 WSDL 的错误信息映射到了在
清单 4
中的 Java 语言异常。
清单 3. 复杂错误的 WSDL 定义
<definitions ...>
<types>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
<element name="InsufficientFundFault">
<complexType>
<sequence>
<element name="balance" type="xsd:int"/>
<element name="requestedFund" type="xsd:int"/>
</sequence>
</complexType>
</element>
</schema>
</types>
<message name="empty"/>
<message name="withdrawRequest">
<part name="amount" type="xsd:int"/>
</message>
<message name="InsufficientFundFaultMessage">
<part name="fault" element="tns:InsufficientFundFault"/>
</message>
<portType name="Bank">
<operation name="withdraw">
<input message="tns:withdrawRequest"/>
<output message="tns:empty"/>
<fault name="fault" message="tns:InsufficientFundFaultMessage"/>
</operation>
</portType>
...
</definitions>
|
清单 4:来自清单 3
中的错误的 Java 异常
public class InsufficientFundFault
extends java.lang.Exception
implements java.io.Serializable {
private int balance;
private int requestedFund;
public InsufficientFundFault(
int balance,
int requestedFund) {
this.balance = balance;
this.requestedFund = requestedFund;
}
public int getBalance() {
return balance;
}
public int getRequestedFund() {
return requestedFund;
}
} |
用于错误继承的映射规则
假设您想要定义一个
InsufficientFundFault
的子类(称为
AccountInsufficientFundFault )来携带请求客户端的帐号(查看
清单 5)。您的应用程序可以抛出这个子类异常,并且您的客户将接收到这个异常。非常简单明了。
清单 5:具有继承性的 WSDL 错误
<wsdl ...>
<types>
<schema targetNamespace="http://example" ...>
...
<complexType name="InsufficientFundFaultType">
<sequence>
<element name="balance" type="xsd:int"/>
<element name="requestedFund" type="xsd:int"/>
</sequence>
</complexType>
<complexType name="AccountInsufficientFundFaultType">
<complexContent>
<extension base="tns:InsufficientFundFaultType">
<sequence>
<element name="account" type="xsd:string"/>
</sequence>
</extension>
</complexContent>
</complexType>
<element name="InsufficientFundFault"
type="tns:InsufficientFundFaultType"/>
...
</schema>
</types>
<message name="AccountInsufficientFundFaultMessage">
<part element="tns:AccountInsufficientFundFault" name="fault"/>
</message>
<operation name="withdraw">
<input message="tns:withdrawRequest" name="withdrawRequest"/>
<output message="tns:withdrawResponse" name="withdrawResponse"/>
<fault message="tns:InsufficientFundFaultMessage"
name="InsufficientFundFault"/>
</operation>
...
</wsdl>
|
通过这样做,生成的 SEI 的方法“withdraw”保持不变(请看
清单 6)。
然而,需要注意的一点就是,
InsufficientFundFault 的任何子类
必须出现在 WSDL
文件中,用于客户端来接收该异常。如果在 WSDL 文件中没有创建
InsufficientFundFault 的一个新的子类,那么您就不能够在服务器端的 Java 组件中创建
InsufficientFundFault 的一个新的子类。WSDL
是一种声明性的语言。服务能够抛出的每一个可能的错误都必须显式地用 XML
定义,否则,客户端将不认识它并且不能进行接收。
清单 6:从上面的 WSDL 定义映射的 Java SEI
public interface Bank extends java.rmi.Remote {
public boolean withdraw(java.lang.String account, int amount)
throws java.rmi.RemoteException,
example.InsufficientFundFaultType;
} |
SOAP
Fault 内容
JAX-RPC runtime 捕获用户异常,并且基于通过
wsdl:fault 的消息部分所引用的 schema 定义来将其序列化成 XML 数据。这种 XML
数据可用来填充 SOAP Fault 的
detail 元素的上下文。
清单 7 是用于复杂
InsufficientFundFault
示例的 SOAP 消息。用于 simpleType 异常的 SOAP
消息与之相似,惟一不同的是
detail 部分。
清单 7:SOAP Fault 示例
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<soapenv:Fault>
<faultcode >...</faultcode>
<faultstring>...</faultstring>
<detail>
<InsufficientFundFault xmlns="http://example">
<balance>1000</balance>
<requestedFund>2000</requestedFund>
</InsufficientFundFault>
</detail>
</soapenv:Fault>
</soapenv:Body>
</soapenv:Envelope>
|
这里的关键是
detail 部分携带的上下文必须与
wsdl:fault 所引用的 schema 相匹配。这样,客户端运行时将知道应该将其映射到哪个用户异常。同样注意,SOAP 错误不携带异常堆栈跟踪(exception stack trace),而这正是您通常对 Java
异常的期望;因此,Web
服务客户端不应该期望看到源自服务器端的堆栈跟踪。
总结
引入用户定义的错误是良好的编程实践。使用
RemoteException 或者
RuntimeException
不仅仅是太一般,而且不能保证每个厂商都会以相同的方式来处理这些异常。
一旦您决定引入用户定义的错误,您就必须决定使用哪种类型的错误——简单类型的错误、复杂类型的错误、或者是错误继承树——并且您必须理解这些错误是如何映射到 Java
编程构件的。
参考资料
作者简介  | |  | Ping Wang 是 IBM WebSphere Web 服务引擎的开发人员之一。先前,他是 WebSphere System Management 的开发人员,专注于如何使用 Java 管理扩展(Java Management eXtension,JMX)来管理分布式进程。您可以通过
pacific@us.ibm.com
与 Ping 联系。
|
 | |  | Russell Butek 是 IBM WebSphere Web 服务引擎的开发人员之一。他也是 JAX-RPC Java Specification Request(JSR) 专家组的 IBM 代表。他从事 Apache 的 AXIS SOAP 引擎的实现方面的研究,推动了 AXIS 1.0 遵循 JAX-RPC 1.0。以前,他是 IBM CORBA ORB 的开发人员和许多 OMG 特别工作组的 IBM 代表:包括可移植拦截器特别工作组(他是这个特别工作组的主席)、核心特别工作组以及互操作性特别工作组。您可以通过
butek@us.ibm.com与 Russell 联系。
|
对本文的评价
|