引入:

前面的例子中我们都是采用了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的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();
                                                                                                                                                                                                                                                                                                                                                                                    
        //构造元素,它的namespace为"http://services.server.cxfstudy.charles.com",注意这个元素在SOAPMessage的部分
        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)









打包并部署应用到服务器上,就可以使用了,最终的wsdl文件如下:







































客户端代码:

现在我们来构造客户端,因为我们的目的是使用直接构造并且发送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 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();
                                                                                                                                                                         
        //构造元素,它的namespace为"http://services.server.cxfstudy.charles.com",注意这个元素在SOAPMessage的部分
        QName calcSumQName = new QName("http://services.server.cxfstudy.charles.com","calcSum");
        SOAPElement calcSumEle = soapRequest.getSOAPBody().addChildElement(calcSumQName);
        //在元素中添加2个子元素,一个为3,一个为5
        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。


看客户端控制台:

wKioL1MIRYfBSWbAAAMEJv3S7UA774.jpg


看服务器端的控制台:

Apache CXF 学习-使用Dispatch/Provider来直接处理SOAP消息_第1张图片


显然,和我们设想的一样,所有现在的处理都是和最终消息打交道,并且web服务也正确的做了加法运算,所以我们代码是完全正确的。


附加说明:

本例演示了如何用Dispatch/Provider发送和处理Service模式是MESSAGE的消息,如果要处理Service模式是PAYLOAD的消息,则应该如下:

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