引入:
前面的例子中我們都是采用了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。
看用戶端控制台:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiInBnauQzN3EUV3M1M2pURNFUQBJ2VTJkZZJVSNFDTvl2S39CX3czLcJTMvwlMw00LcJDMzZWe39CXt92Yu8GdjFTNuMzcvw1LcpDc0RHaiojIsJye.jpg)
看伺服器端的控制台:
顯然,和我們設想的一樣,所有現在的處理都是和最終消息打交道,并且web服務也正确的做了加法運算,是以我們代碼是完全正确的。
@WebServiceProvider()
@ServiceMode(value=Service.Mode.PAYLOAD)
public class CalcMinusServiceProvider implements Provider<DOMSource> {