天天看點

Apache CXF 學習-使用Dispatch/Provider來直接處理SOAP消息

引入:

前面的例子中我們都是采用了SEI/SIB的方式來發送接收消息,其實我們用戶端的代碼是直接采用的傳統的API調用,比如:

service.calcSum(a, b)      

然後,在CXF架構中,它會把這些API調用的方式通過JAXB轉為SOAP格式的消息,然後傳回SOAP格式的消息也通過JAXB轉回真正的傳回值。是以這裡的弊端是,雖然真正在網絡上傳輸的是SOAP消息,但是我們卻依然用傳統的調用方式操作,顯的多此一舉。如果一個對象很大,那麼将其通過JAXB轉為SOAP消息則會花費一定的時間。那麼有沒有辦法可以讓我們用戶端和伺服器端都直接對SOAP消息操作呢?這就需要我們這裡讨論的Dispatch/Provider技術。

實踐:

Dispatch/Provider總是成對用的,用戶端一般會構造一個SOAP消息,然後把它Dispatch到伺服器的Endpoint之上,這就是Dispatch.而伺服器端會給出如何對約定的SOAP消息格式進行處理并且構造傳回消息的代碼,這就叫Provider。 從對于消息的處理方式上看, 有直接處理整個消息的,對應就是Service.Mode.MESSAGE,也有隻處理消息Payload的,對應就是Service.Mode.PAYLOAD,我們這裡隻示範Service.Mode.MESSAGE,另外一個和這個用法類似。

伺服器端代碼:

還是從伺服器端開始,首先我們定義一個消息處理類CalcPlusServiceProvider,它可以處理整個SOAP請求消息并且構造傳回SOAP消息,我們讓其邏輯為隻對請求的SOAP消息中的2個參數做加法運算,然後運算結果封裝在傳回SOAP消息中,并且代碼中會分别把請求消息和響應消息列印到伺服器的控制台上。代碼如下:

package com.charles.cxfstudy.provider;
import java.io.IOException;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.dom.DOMSource;
import javax.xml.ws.Provider;
import javax.xml.ws.Service;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceProvider;
import org.w3c.dom.Node;
/**
 * 這個加法運算類用于示範基于Message模式的Provider,它會把消息作為整體來處理
 * @author Administrator
 *
 */
@WebServiceProvider()
@ServiceMode(value=Service.Mode.MESSAGE)
public class CalcPlusServiceProvider implements Provider<DOMSource> {
    /**
     * 這個方法用于定義如何處理DOMSource的XML消息的邏輯,并且構造響應消息
     */
    public DOMSource invoke(DOMSource request) {
        try{
        //先構造 一個SOAPMessage,用于放入請求的SOAP消息
        MessageFactory factory = MessageFactory.newInstance();
        SOAPMessage soapRequestMsg = factory.createMessage();
        //注意,因為我們的代碼是吧消息作為整體處理,是以放入的是soapPart,而不是soapBody
        soapRequestMsg.getSOAPPart().setContent(request);
                                                                                                                                                                                                                                                                                                                                                                                    
        //列印到用戶端請求來的消息到控制台
        System.out.println("從用戶端請求來的消息為:");
        soapRequestMsg.writeTo(System.out);
        System.out.println();
                                                                                                                                                                                                                                                                                                                                                                                    
        //現在我們從請求消息中分離出我們所要的資訊
        SOAPBody soapBody = soapRequestMsg.getSOAPBody();
                                                                                                                                                                                                                                                                                                                                                                                    
        Node calcSumNode = soapBody.getFirstChild();
                                                                                                                                                                                                                                                                                                                                                                                    
        //獲得要做加法運算的數
        Node aNode = calcSumNode.getChildNodes().item(0);
        int a = Integer.parseInt(aNode.getTextContent());
        Node bNode = calcSumNode.getChildNodes().item(1);
        int b = Integer.parseInt(bNode.getTextContent());
                                                                                                                                                                                                                                                                                                                                                                                        
        //計算加法
        String sum = String.valueOf(a + b);
                                                                                                                                                                                                                                                                                                                                                                                    
        //封裝結果到響應對象中
        SOAPMessage soapResponseMsg = factory.createMessage();
                                                                                                                                                                                                                                                                                                                                                                                    
        //構造<calcSumResponse>元素,它的namespace為"http://services.server.cxfstudy.charles.com",注意這個元素在SOAPMessage的<soap:Body>部分
        QName calcSumResponseQName = new QName("http://services.server.cxfstudy.charles.com","calcSumResponse");
        SOAPElement calcSumResponseEle = soapResponseMsg.getSOAPBody().addChildElement(calcSumResponseQName);
        calcSumResponseEle.addChildElement("sum").addTextNode(sum);
                                                                                                                                                                                                                                                                                                                                                                                    
        //列印即将傳回到用戶端的響應消息到控制台
        System.out.println("要發送到用戶端的消息為:");
        soapResponseMsg.writeTo(System.out);
        System.out.println();
                                                                                                                                                                                                                                                                                                                                                                                    
        //把SOAPMessage轉為DOMSource類型
        DOMSource response = new DOMSource(soapResponseMsg.getSOAPPart());
        return response;
                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                    
        }catch(SOAPException ex){
            ex.printStackTrace();
            return null;
        }catch(IOException ex){
            ex.printStackTrace();
            return null;
        }
                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                    
    }
}      

為了讓這個服務類生效,我們配置到beans.xml中(參見http://supercharles888.blog.51cto.com/609344/1361334)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jaxws="http://cxf.apache.org/jaxws"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<!-- 導入cxf中的spring的一些配置檔案,他們都在cxf-<version>.jar檔案中 -->
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<jaxws:endpoint
id="calcPlusService"
implementor="com.charles.cxfstudy.provider.CalcPlusServiceProvider"
address="/calcPlus" />
</beans>      

打包并部署應用到伺服器上,就可以使用了,最終的wsdl檔案如下:

<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://provider.cxfstudy.charles.com/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="CalcPlusServiceProviderService" targetNamespace="http://provider.cxfstudy.charles.com/">
<wsdl:types>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://provider.cxfstudy.charles.com/" attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://provider.cxfstudy.charles.com/">
<xsd:element name="invoke" nillable="true" type="xsd:anyType"/>
<xsd:element name="invokeResponse" nillable="true" type="xsd:anyType"/>
</xsd:schema>
</wsdl:types>
<wsdl:message name="invokeResponse">
<wsdl:part element="tns:invokeResponse" name="invokeResponse"></wsdl:part>
</wsdl:message>
<wsdl:message name="invoke">
<wsdl:part element="tns:invoke" name="invoke"></wsdl:part>
</wsdl:message>
<wsdl:portType name="CalcPlusServiceProvider">
<wsdl:operation name="invoke">
<wsdl:input message="tns:invoke" name="invoke"></wsdl:input>
<wsdl:output message="tns:invokeResponse" name="invokeResponse"></wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="CalcPlusServiceProviderServiceSoapBinding" type="tns:CalcPlusServiceProvider">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="invoke">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="invoke">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="invokeResponse">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="CalcPlusServiceProviderService">
<wsdl:port binding="tns:CalcPlusServiceProviderServiceSoapBinding" name="CalcPlusServiceProviderPort">
<soap:address location="http://localhost:8080/cxf_jaxws_provider/services/calcPlus"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>      

用戶端代碼:

現在我們來構造用戶端,因為我們的目的是使用直接構造并且發送SOAP消息的方式而不是類似SEI調用的方式來發送消息,是以我們先定義工具類,内含一個工具方法可以發送SOAP消息并且獲得從伺服器端的傳回消息:

package com.charles.cxfstudy.dispatcher;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.dom.DOMSource;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import javax.xml.ws.Service.Mode;
/**
 * 工具類用于發送和接收消息
 * @author Administrator
 *
 */
public class DispatcherUtil {
                                                                                                                                                                                                                                                              
    /**
     * 把指定的SOAP消息發送到指定的endpoint上,并且給出傳回的SOAP消息
     * @param wsdlURLString
     * @param serviceQName
     * @param serviceProviderServiceName
     * @param serviceProviderPortName
     * @param soapRequest
     * @param factory
     * @return
     * @throws MalformedURLException
     * @throws SOAPException
     */
    public static SOAPMessage sendMessage (
            String wsdlURLString, String serviceQName,String serviceProviderServiceName,String serviceProviderPortName,SOAPMessage soapRequest,MessageFactory factory) throws MalformedURLException,SOAPException{
        //把SOAPMessage轉為Source類型
        DOMSource requestMsg =  new DOMSource(soapRequest.getSOAPPart());
                                                                                                                                                                                                                                                                  
        URL wsdlURL = new URL(wsdlURLString);
                                                                                                                                                                                                                                                                  
        //構造一個Service對象
        QName serviceProvider = new QName(serviceQName,serviceProviderServiceName);
        QName portName        = new QName(serviceQName,serviceProviderPortName);
        Service service  = Service.create(wsdlURL, serviceProvider);
                                                                                                                                                                                                                                                                  
        //利用Service對象來發送(Dispatch) Source類型的SOAPMessage到指定的Port上
        Dispatch<DOMSource> domMsg  = service.createDispatch(portName, DOMSource.class, Mode.MESSAGE);
        //獲得響應消息
        DOMSource respMsg = domMsg.invoke(requestMsg);
        SOAPMessage soapResponse = factory.createMessage();
        soapResponse.getSOAPPart().setContent(respMsg);
                                                                                                                                                                                                                                                                  
        return soapResponse;
                                                                                                                                                                                                                                                                                  
    }
}      

注意:我非常喜歡這種方式,因為它最直接了,發送什麼消息就構造什麼消息,然後直接調用API,而無需用wsimport工具去操作WSDL檔案去生成N多樁檔案了。

然後我們的測試類的方法就是構造SOAP消息,然後調用工具方法來發送SOAP消息并且擷取傳回消息,并且分别列印到用戶端的控制台上:

/**
 * 這裡用于示範如何用Dispatch來發送一個SOAP消息到指定的Provider
 * @author Administrator
 *
 */
public class MainTest {
                                                                                                                                                                     
    public static SOAPMessage buildMessageForAdd(MessageFactory factory) throws SOAPException{
                                                                                                                                                                         
        SOAPMessage soapRequest = factory.createMessage();
                                                                                                                                                                         
        //構造<calcSum>元素,它的namespace為"http://services.server.cxfstudy.charles.com",注意這個元素在SOAPMessage的<soap:Body>部分
        QName calcSumQName = new QName("http://services.server.cxfstudy.charles.com","calcSum");
        SOAPElement calcSumEle = soapRequest.getSOAPBody().addChildElement(calcSumQName);
        //在<calcSum>元素中添加2個子元素,一個為<a>3</a>,一個為<b>5</b>
        calcSumEle.addChildElement("a").addTextNode("3");
        calcSumEle.addChildElement("b").addTextNode("5");
                                                                                                                                                                         
        return soapRequest;
                                                                                                                                                                         
                                                                                                                                                                         
    }
                                                                                                                                                                     
                                                                                                                                                                     
                                                                                                                                                                     
    public static void main(String [] args) throws Exception {
                                                                                                                                                                         
        String wsdlURLStringForCalcPlus = "http://localhost:8080/cxf_jaxws_provider/services/calcPlus?wsdl";
        String serviceQName            =  "http://provider.cxfstudy.charles.com/";
        String serviceProviderStringForCalcPlus = "CalcPlusServiceProviderService";
        String servicePortStringForCalcPlus = "CalcPlusServiceProviderPort";
                                                                                                                                                                         
        //構造要發送的Soap消息内容并且轉為Source類型
        //從MessageFactory 構造一個要發送的Soap消息
        MessageFactory factory = MessageFactory.newInstance();
                                                                                                                                                                         
        SOAPMessage soapRequest= buildMessageForAdd(factory);
        System.out.println("發送的消息為:");
        soapRequest.writeTo(System.out);
        System.out.println();
                                                                                                                                                                         
                                                                                                                                                                         
                                                                                                                                                                         
        SOAPMessage soapResponse =DispatcherUtil.sendMessage(
                wsdlURLStringForCalcPlus,serviceQName,serviceProviderStringForCalcPlus,servicePortStringForCalcPlus,soapRequest, factory);
        System.out.println("響應的消息為:");
        soapResponse.writeTo(System.out);
                                                                                                                                                                         
                                                                                                                                                                         
                                                                                                                                                                         
        }
}      

從上看出,我們構造了一個消息,其包含2個數,一個是3,一個是5,我們期望通過web service計算加法後傳回8。

看用戶端控制台:

Apache CXF 學習-使用Dispatch/Provider來直接處理SOAP消息

看伺服器端的控制台:

Apache CXF 學習-使用Dispatch/Provider來直接處理SOAP消息

顯然,和我們設想的一樣,所有現在的處理都是和最終消息打交道,并且web服務也正确的做了加法運算,是以我們代碼是完全正确的。

@WebServiceProvider()
@ServiceMode(value=Service.Mode.PAYLOAD)
public class CalcMinusServiceProvider implements Provider<DOMSource> {      

繼續閱讀