使用CXF Interceptor特性

     在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;  
    }  
}

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;  
    }  
}

開發客戶端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;  
    }  
}

客戶端添加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);  
    }  
}

開發發佈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);  
    }  
}
 

理解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未提供任何值,那麼默認將執行所有內容壓縮。
相關文章
相關標籤/搜索