内容


Web 服务编程技巧与窍门

通过 JAX-RPC 来处理异常

从服务端点抛出正确的异常

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: Web 服务编程技巧与窍门

敬请期待该系列的后续内容。

此内容是该系列的一部分:Web 服务编程技巧与窍门

敬请期待该系列的后续内容。

在 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 映射到 RemoteExceptionSOAPFaultException (将在下面描述)。因此,服务端点不应该抛出 RuntimeException ,期望客户端总会捕获 RuntimeException ,因为客户端可能接收到 RemoteException 来代替。

SOAPFaultException

有一个特殊的 RuntimeException :javax.xml.rpc.soap.SOAPFaultException。 SOAPFaultExceptionRuntimeException 更具描述性,它规定了传送到客户端的确切 SOAP Fault 消息。换句话说,究竟是谁,是运行时还是应用程序,控制着 SOAP Fault 响应。因此,如何将 SOAP fault 映射到合适的异常实际上依赖于 SOAPFaultException 的上下文,它可能被映射到 SOAPFaultExceptionRemoteException 或者甚至是已检查的用户异常。 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:inputwsdl: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 编程构件的。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=SOA and web services
ArticleID=300801
ArticleTitle=Web 服务编程技巧与窍门: 通过 JAX-RPC 来处理异常
publish-date=02012004