使用CXF Interceptor特性

jopen 11年前發布 | 59K 次閱讀 Apache CXF WEB服務/RPC/SOA

     在Web Service中,客戶端和服務端通過交換信息來互相通信。信息在客戶端組裝,在服務端解組。在Web Service術語中,組裝表示將JAVA對象轉換為XML文件,這些XML文檔將被傳輸到網絡中;反而言之,解組就是將XML文檔轉換為JAVA對象。

     當客戶端向服務端發送請求,請求中的數據將被組裝并傳輸到服務器。服務器獲取該數據,解組,最后調用服務方法。當服務器發送響應給客戶端時,將重復該過程。組裝和解組是客戶端和服務端提供的核心功能。CXF通過Interceptor來提供這些功能。

     Interceptor通過監聽傳輸過來的信息來提供核心功能。這些功能包括:組裝、解組、操縱消息頭、執行認證檢查、驗證消息數據等。CXF提供內置的Interceptor來實現這些功能。用戶也可以自定義Interceptor。Interceptor以phases組織起來,以鏈的形式調用, 

理解interceptor phase 和chain

    Phase可以被當作分類框,將類似功能的Interceptor組織起來。

    有兩種Interceptor鏈,inbound鏈和outbound鏈。兩種都有一系列的Phase。例如,在inbound鏈中有UNMARSHAL Phase,用來解組信息數據,并將其轉化為JAVA對象。

     對于每個請求來講,在服務端創建inbound Interceptor;對于每個響應來講,將創建outbound Interceptor。

消息傳輸到鏈中,按特定的順序在Phase中的Interceptor執行。

在信息傳輸到服務端之前,inbound Interceptor操作信息。

在信息傳輸到客戶端之前,outbound Interceptor操作信息。

如果出現錯誤,Interceptor鏈釋放自己,不去調用應用。

interceptor API概覽

PhaseInterceptor繼承自Interceptor接口。AbstractPhaseInterceptor實現了PhaseInterceptor。

Interceptor接口

     如果要自定義Interceptor,就必須直接或間接實現Interceptor接口。Interceptor接口定義了兩個方法。handleMessage和handleFault。

package org.apache.cxf.interceptor;

public interface Interceptor<T extends Message> {

   void handleMessage(T message) throws Fault;

   void handleFault(T message);

}

    handleMessage方法需要org.apache.cxf.message.Message對象。這是執行信息的核心方法。為了自定義Interceptor就必須實習該方法,并提供執行信息的邏輯。

    handleFault方法同樣需要org.apache.cxf.message.Message對象。在執行Interceptor過程中出現錯誤時將會調用該方法。

PhaseInterceptor接口

    大多數核心Interceptor都是實現繼承自Interceptor接口的PhaseInterceptor。

PhaseInterceptor定義了4個方法。

getAfter將返回一個Set,包含了在這個Interceptor之前執行Interceptor的IDs。

getBefore將返回一個Set,包含了在這個Interceptor之后執行Interceptor的IDs。

getId返回ID

getPhase返回這個Interceptor所在的phase。

為了自定義Interceptor,開發者必須繼承AbstractPhaseInterceptor抽象類。

AbstractPhaseInterceptor抽象類

   AbstractPhaseInterceptor定義了構造器,可以為自定義的Interceptor指定特定的phase。當你指定了phase,自定義的Interceptor將會安排在鏈中Phase。AbstractPhaseInterceptor提供了空實現的handleFault。開發者可以覆蓋這個方法。開發者必須實現handleMessage方法。

   查看AbstractPhaseInterceptor抽象類,它已實現PhaseInterceptor接口的方法。Phase的順序由PhaseInterceptorChain類決定

開發自定義的interceptor

      為了演示interceptor的功能,假定一個用例:只有經過認證的用戶才能調用Web Service,用戶認證需要獲取SOAP頭的信息。

      為了實現這些需求,創建兩個interceptor,一個客戶端,一個服務端。客戶端interceptor負責攔截即將發出的SOAP信息,并在SOAP頭中添加用戶驗證。服務端interceptor負責攔截收到的SOAP信息,從SOAP信息獲取用戶認證信息并驗證該用戶。如果用戶驗證失敗,將拋出異常,在這種情況下阻止Web Service的運行。

開發自定義的interceptor分為如下幾個步驟:

       開發服務端的interceptor。

       在Web Service服務類添加服務端interceptor。

       開發客戶端interceptor。

       在客戶端添加客戶端interceptor。

       開發發布Web Service的服務器。

開發服務端interceptor  

OrderProcessUserCredentialInterceptor繼承自AbstractSoapInterceptor,AbstractSoapInterceptor繼承自AbstractPhaseInterceptor。AbstractSoapInterceptor提供了獲取SOAP頭和版本的信息。

OrderProcessUserCredentialInterceptor默認構造函數中,指定了Phase.PRE_INVOKE,代表著該interceptor將先于Web Service服務類執行。

 
public OrderProcessUserCredentialInterceptor() {
        super(Phase.PRE_INVOKE);
    }

handleMessage方法中,SoapMessage提供了獲取SOAP頭的信息。使用message.getHeader獲取SOAP頭中<OrderCredentials>元素

Header header = message.getHeader(qnameCredentials);

 
<soap:Header>
      <OrderCredentials>
         <username>John</username>
         <password>password</password>
      </OrderCredentials>
</soap:Header>

在OrderCredentials節點下,可以獲得用戶名和密碼節點。

Element elementOrderCredential = (Element) header.getObject();
Node nodeUser = elementOrderCredential.getFirstChild();
Node nodePassword = elementOrderCredential.getLastChild();
 
package demo.order.server;

import javax.xml.namespace.QName;

import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

public class OrderProcessUserCredentialInterceptor extends AbstractSoapInterceptor {

private String userName;  
private String password;  

public OrderProcessUserCredentialInterceptor() {  
    super(Phase.PRE_INVOKE);  
}  

public void handleMessage(SoapMessage message) throws Fault {  

    System.out.println("OrderProcessUserCredentialInterceptor handleMessage invoked");  
    QName qnameCredentials = new QName("OrderCredentials");  

    // Get header based on QNAME  
    if (message.hasHeader(qnameCredentials)) {  
        Header header = message.getHeader(qnameCredentials);  

        Element elementOrderCredential = (Element) header.getObject();  
        Node nodeUser = elementOrderCredential.getFirstChild();  
        Node nodePassword = elementOrderCredential.getLastChild();  

        if (nodeUser != null) {  
            userName = nodeUser.getTextContent();  
        }  
        if (nodePassword != null) {  
            password = nodePassword.getTextContent();  
        }  
    }  

    System.out.println("userName reterived from SOAP Header is " + userName);  
    System.out.println("password reterived from SOAP Header is " + password);  

    // Perform dummy validation for John  
    if ("John".equalsIgnoreCase(userName) && "password".equalsIgnoreCase(password)) {  
        System.out.println("Authentication successful for John");  
    } else {  
        throw new RuntimeException("Invalid user or password");  
    }  
}  

public String getUserName() {  
    return userName;  
}  

public void setUserName(String userName) {  
    this.userName = userName;  
}  

public String getPassword() {  
    return password;  
}  

public void setPassword(String password) {  
    this.password = password;  
}  

} </pre>

Web Service業務類添加interceptor

Web Service業務類可以添加配置文件或者定義annotations添加interceptor。

添加InInterceptors注釋定義了一個Inbound interceptor,將在Web Service執行之前調用。  

@org.apache.cxf.interceptor.InInterceptors (interceptors ={"demo. order.server.OrderProcessUserCredentialInterceptor" })

package demo.order;

import javax.jws.WebService;

@org.apache.cxf.interceptor.InInterceptors(interceptors = {"demo.order.server.OrderProcessUserCredentialInterceptor"})
@WebService
public class OrderProcessImpl implements OrderProcess {

public String processOrder(Order order) {  
    System.out.println("Processing order...");  
    String orderID = validate(order);  
    return orderID;  
}  

/** 
 * Validates the order and returns the order ID 
* 
 */  
private String validate(Order order) {  
    String custID = order.getCustomerID();  
    String itemID = order.getItemID();  
    int qty = order.getQty();  
    double price = order.getPrice();  

    if (custID != null && itemID != null && qty > 0 && price > 0.0) {  
        return "ORD1234";  
    }  

    return null;  
}  

} </pre>

開發客戶端interceptor

同樣的OrderProcessClientHandler繼承自AbstractSoapInterceptor。

OrderProcessClientHandler默認構造器,指定了super(Phase.WRITE),還addAfter方法。addAfter指定了OrderProcessClientHandler將在CXF內置SoapPreProtocolOutInterceptor之后添加。OrderProcessClientHandler將在Phase.WRITE并在SoapPreProtocolOutInterceptor之后運行。SoapPreProtocolOutInterceptor負責建立SOAP版本和頭,因此任何SOAP頭元素的操作都必須在SoapPreProtocolOutInterceptor之后執行。

public OrderProcessClientHandler() {
      super(Phase.WRITE);
      addAfter(SoapPreProtocolOutInterceptor.class.getName());
}

構造函數new Header(qnameCredentials, elementCredentials);創建了Header對象,并設置了OrderCredentials元素。最后message.getHeaders().add(header)將SOAP頭添加到信息中。

package demo.order.client;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class OrderProcessClientHandler extends AbstractSoapInterceptor {

public String userName;  
public String password;  

public OrderProcessClientHandler() {  
    super(Phase.WRITE);  
    addAfter(SoapPreProtocolOutInterceptor.class.getName());  
}  

public void handleMessage(SoapMessage message) throws Fault {  

    System.out.println("OrderProcessClientHandler handleMessage invoked");  

    DocumentBuilder builder = null;  
    try {  
        builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();  
    } catch (ParserConfigurationException e) {  
        e.printStackTrace();  
    }  
    Document doc = builder.newDocument();  
    Element elementCredentials = doc.createElement("OrderCredentials");  
    Element elementUser = doc.createElement("username");  
    elementUser.setTextContent(getUserName());  
    Element elementPassword = doc.createElement("password");  
    elementPassword.setTextContent(getPassword());  
    elementCredentials.appendChild(elementUser);  
    elementCredentials.appendChild(elementPassword);  

    // Create Header object  
    QName qnameCredentials = new QName("OrderCredentials");  
    Header header = new Header(qnameCredentials, elementCredentials);  
    message.getHeaders().add(header);  
}  

public String getPassword() {  
    return password;  
}  

public void setPassword(String password) {  
    this.password = password;  
}  

public String getUserName() {  
    return userName;  
}  

public void setUserName(String userName) {  
    this.userName = userName;  
}  

} </pre>

客戶端添加interceptor

OrderProcessClientHandler設置了username和password。通過ClientProxy.getClient(client);獲取Client對象,并通過cxfClient.getOutInterceptors().add(clientInterceptor)將clientInterceptor作為outbound interceptor。

package demo.order.client;

import demo.order.OrderProcess;
import demo.order.Order;

import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.gzip.GZIPInInterceptor;
import org.apache.cxf.transport.http.gzip.GZIPOutInterceptor;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Client {

public Client() {  
}  

public static void main(String args[]) throws Exception {  
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"client-beans.xml"});  

    OrderProcess client = (OrderProcess) context.getBean("orderClient");  

// OrderProcess client = (OrderProcess) context.getBean("orderClient2");
OrderProcessClientHandler clientInterceptor = new OrderProcessClientHandler();
clientInterceptor.setUserName("John");
clientInterceptor.setPassword("password");
org.apache.cxf.endpoint.Client cxfClient = ClientProxy.getClient(client);
cxfClient.getOutInterceptors().add(clientInterceptor);

    Order order = new Order();  
    order.setCustomerID("C001");  
    order.setItemID("I001");  
    order.setQty(100);  
    order.setPrice(200.00);  

    String orderID = client.processOrder(order);  
    String message = (orderID == null) ? "Order not approved" : "Order approved; order ID is " + orderID;  
    System.out.println(message);  
}  

} </pre>

開發發布Web Service的服務器

package demo.order;

import org.apache.cxf.feature.LoggingFeature;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
import org.apache.cxf.transport.http.gzip.GZIPFeature;
import org.apache.cxf.transport.http.gzip.GZIPInInterceptor;
import org.apache.cxf.transport.http.gzip.GZIPOutInterceptor;

public class OrderProcessServerStart {

public static void main(String[] args) throws Exception {  

    OrderProcess orderProcess = new OrderProcessImpl();  
    LoggingFeature log = new LoggingFeature();  
    GZIPFeature gzip = new GZIPFeature();  
    gzip.setThreshold(1);  
    JaxWsServerFactoryBean server = new JaxWsServerFactoryBean();  
    server.setServiceBean(orderProcess);  
    server.setAddress("http://localhost:8080/OrderProcess");  

    server.getFeatures().add(log);  
    server.getFeatures().add(gzip);  
    //server.getFeatures().add(log);  
    //server.getInInterceptors().add(new LoggingInInterceptor());  
    //server.getOutInterceptors().add(new LoggingOutInterceptor());  


    server.create();  
    System.out.println("Server ready....");  



    Thread.sleep(5 * 60 * 1000);  
    System.out.println("Server exiting");  
    System.exit(0);  
}  

} </pre> 

理解CXF features

Feature其實是interceptors另外一種形式。可以使用feature組件來代替直接使用interceptor。

本例中添加了log和gzip兩個feature,正如Interceptor中所描述的phase的概念,添加log和gzip的順序會影響LoggingFeature顯示的內容。

請讀者自行嘗試OrderProcessServerStart中

        server.getFeatures().add(log);

        server.getFeatures().add(gzip);

        //server.getFeatures().add(log);

和client-beans.xml中

 
   <bean class="org.apache.cxf.feature.LoggingFeature"></bean>
   <bean class="org.apache.cxf.transport.http.gzip.GZIPFeature" >  
   <property name="threshold" value="1" /></bean>
   <!--bean class="org.apache.cxf.feature.LoggingFeature"></bean-->

         

安排log和gzip的添加次序。

LoggingFeature

LoggingFeature可以獨立于服務端和客戶端。也就是說,可以服務端添加LoggingFeature,而客戶端不添加,反之亦然。

GZIPFeature

GZIPFeature不同于LoggingFeature,客戶端和服務端必須同時實現GZIPFeature,不然會拋出異常javax.xml.ws.soap.SOAPFaultException: Couldn't parse stream.。

查看添加GZIPFeature的服務器需要借助于工具。Tcpmon是其中的一個選擇,另外LoggingFeature提供了tcpmon類似的功能。

在client-beans.xml中<http-conf:client>元素指定了AcceptEncoding屬性,這個屬性暗示客戶端應用將會接受gzip的內容。

tcpmon

如何用Apache TCPMon來截獲SOAP消息

http://www.blogjava.net/heyang/archive/2010/12/10/340294.html

下載地址http://ws.apache.org/commons/tcpmon/download.cgi

 
server.setAddress("http://localhost:8080/OrderProcess");

在執行CXF過程中,是無法查看到組裝和解組的內容。如果希望看到內容,則需要借助工具。外部工具如tcpmon。將client-beans.xml address中的端口修改你希望的端口,如8081。然后在OrderProcessServerStart查看發布的端口 為8080。在tcpmon解壓的目錄bin中執行tcpmon.bat,出現tcpmon界面。切換到Admin。在Listen Port #處填寫8081,在Target Port #填寫8080,點擊Add。然后切換到Sender。為了方便查看XML,在左下角有個XML Format,將其勾選上,tcpmon會將捕獲的內容整理為XML格式。執行Client,便可以再Sender界面查看到XML的具體內容了。

threshold

忽略1 byte并壓縮剩下的內容。Threshold若為0值表示全部內容都將壓縮。如果Threshold未提供任何值,那么默認將執行全部內容壓縮。

來自:http://reymont.iteye.com/blog/1532463

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!