引入: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> {