引入:java
前面的例子中咱們都是採用了SEI/SIB的方式來發送接收消息,其實咱們客戶端的代碼是直接採用的傳統的API調用,好比:web
service.calcSum(a, b)
而後,在CXF框架中,它會把這些API調用的方式經過JAXB轉爲SOAP格式的消息,而後返回SOAP格式的消息也經過JAXB轉回真正的返回值。因此這裏的弊端是,雖然真正在網絡上傳輸的是SOAP消息,可是咱們卻依然用傳統的調用方式操做,顯的畫蛇添足。若是一個對象很大,那麼將其經過JAXB轉爲SOAP消息則會花費必定的時間。那麼有沒有辦法可讓咱們客戶端和服務器端都直接對SOAP消息操做呢?這就須要咱們這裏討論的Dispatch/Provider技術。spring
實踐:apache
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)dom
<?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文件以下:ide
<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。
看客戶端控制檯:
看服務器端的控制檯:
顯然,和咱們設想的同樣,全部如今的處理都是和最終消息打交道,而且web服務也正確的作了加法運算,因此咱們代碼是徹底正確的。
附加說明:
本例演示瞭如何用Dispatch/Provider發送和處理Service模式是MESSAGE的消息,若是要處理Service模式是PAYLOAD的消息,則應該以下:
@WebServiceProvider() @ServiceMode(value=Service.Mode.PAYLOAD) public class CalcMinusServiceProvider implements Provider<DOMSource> {