引入:
前面的例子中我们都是采用了SEI/SIB的方式来发送接收消息,其实我们客户端的代码是直接采用的传统的API调用,比如:
1
|
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消息中,并且代码中会分别把请求消息和响应消息打印到服务器的控制台上。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<?
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文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
<
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消息并且获得从服务器端的返回消息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
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消息并且获取返回消息,并且分别打印到客户端的控制台上:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
/**
* 这里用于演示如何用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的消息,则应该如下:
1
2
3
|
@WebServiceProvider
()
@ServiceMode
(value=Service.Mode.PAYLOAD)
public
class
CalcMinusServiceProvider
implements
Provider<DOMSource> {
|