使用CXF Interceptor特性
在Web Service中,客戶端和服務端通過交換信息來互相通信。信息在客戶端組裝,在服務端解組。在Web Service術語中,組裝表示將JAVA對象轉換為XML文件,這些XML文檔將被傳輸到網絡中;反而言之,解組就是將XML文檔轉換為JAVA對象。
理解interceptor phase 和chain
interceptor API概覽
Interceptor接口
PhaseInterceptor接口
AbstractPhaseInterceptor抽象類
開發自定義的interceptor
public OrderProcessUserCredentialInterceptor() { super(Phase.PRE_INVOKE); }
Header header = message.getHeader(qnameCredentials);
<soap:Header> <OrderCredentials> <username>John</username> <password>password</password> </OrderCredentials> </soap:Header>
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.cgiserver.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未提供任何值,那么默認將執行全部內容壓縮。