在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未提供任何值,那麼默認將執行所有內容壓縮。