回顾之前的文章,WebService SOAP消息是通过HTTP协议传输的。这个消息传给谁,以及需要执行什么action,均是由http header中的值决定:
POST?/library/service?HTTP?1.1/POST????????????????????
Host:?localhost:8080
SOAPAction:?http://library.mycompany.com/Library/addBookRequest
这使得一个SOAP message,依赖于传输协议。如果不适用HTTP,而使用SMTP,那么需要重新设计程序对SMTP的解析。
为了杜绝消息对传输协议的依赖,W3C组织退出了WS Addressing规范。
WS Addressing规范
Web服务寻址为Web服务提供一种与传输层无关的,传送寻址信息的机制。规范主要由两部分组成:传送Web服务端点的引用的数据结构,以及一套能够在特定的消息上关联寻址信息的消息寻址属性。
其主要做法是在SOAP消息的Head中,嵌入寻址相关的属性:
目的地 -- 该消息的目的地的URI。
源端点 -- 发出该消息的服务端点(EPR - EndPoint Referrence)
应答端点 -- 应答消息接收者的端点(EPR)
故障端点 -- 故障消息接收者的端点(EPR)
动作-- 指示该消息的语义(可能有助于该消息的寻址)的URI 如http://library.mycompany.com/Library/addBookRequest
消息ID -- 唯一消息标识符UUID
关系 -- 与之前消息的关系(之前消息的UUID)
通过以上做法,可以使得SOAP消息完全自成一个自给自足小世界。脱离了任何传输协议,任何一个agent都可以根据message知道它来自哪,去向哪。这就产生了以下几种实用场景:
接下来,我们以开发实例来说明寻址功能。
WS-Addressing实例
WS-Addressing的开发与WS开发没区别,可以从JAVA开始,也可以从WSDL开始。作为一个Java程序员,最简单的还是从java开始。
服务器端
package?com.mycompany;
import?javax.jws.WebService;
import?javax.xml.ws.Action;
import?javax.xml.ws.soap.Addressing;
@WebService(targetNamespace="http://mycompany.com")
@Addressing(enabled=true,?required=true,?responses=AddressingFeature.Responses.ALL)
public?class?Say?{
????@Action?(input="http://mycompany.com/say/helloJava/request",?
???? output="http://mycompany.com/say/helloJava/response")
????public?String?helloJava()?{
????????return?"Java";
????}
????
????//?如果没有声明Action,则由程序自动产生。算法为
????//?[target?namespace][delimiter][port?type?name][delimiter][input|output?name]
????public?String?helloPython()?{
????????return?"Python";
????}
}
通过上面的代码,可以看到使用annotation @Addressing可以开启WS Addressing服务,使用@Action可以命名动作名称。
@Addressing有三个属性,这三个属性都是可选:
enabled,默认是true,表明开启addressing。
required,默认是false,表明是否使用addressing功能。如果enabled和required都是true,那么SOAP header中必须出现addressing属性(MAP)。
responses,默认为ALL,如果使用非匿名则表明消息发起方不能使匿名的,必须有EPR。
部署
之前的文章已经多次写过部署步骤,往后不再重复。
生成sun-jaxws.xml,
<endpoints?xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime'?version='2.0'>
????<endpoint
????????name='say'
????????implementation='com.mycompany.Say'
????????url-pattern='/service'/>
</endpoints>
然后将代码编译打包成say.war,然后部署到tomcat。
访问?http://localhost:8080/say/service?wsdl?
查看WSDL
观察WSDL,看看开启了WS Addressing的WSDL与以往有何不同。
首先,新的WSDL比以往多了policy的声明。在Policy中,声明WS addressing不允许匿名访问。

其次,在portType中operation中的input和output,均多了wsam:action属性,这个属性会显示在SOAP header中的action中。

最后,binding中会声明port使用WS Addressing。

客户端
使用wsimport命令,生成客户端程序。之前的文章做过太多叙述,从今往后,省去wsimport相关描述。
调用客户端程序:
public?class?App?{
????public?static?void?main(?String[]?args?)??{
????????test2();
????}
????
????public?static?void?test2()?{
???? Say?say?=?new?SayService().getSayPort(new?AddressingFeature(true,true,Responses.NON_ANONYMOUS));
???? System.out.println(say.helloJava());
???? System.out.println(say.helloPython());
????}
}
运行客户端程序。
消息
接下来,让我们查看上面的程序,客户端和服务器端交换的消息体:
1,say.helloJava()请求
<S:Envelope?xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"?xmlns:wsa="http://www.w3.org/2005/08/addressing"?>
??<S:Header>
????<wsa:To>http://localhost:8080/say/service</wsa:To>
????<wsa:Action?S:mustUnderstand="1">http://mycompany.com/say/helloJava/request</wsa:Action>
????<wsa:ReplyTo>
??????<wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
????</wsa:ReplyTo>
????<wsa:MessageID>uuid:3fff81b3-2573-40b8-87f6-2b77b9e84d94</wsa:MessageID>
??</S:Header>
??<S:Body>
????<ns2:helloJava?xmlns:ns2="http://mycompany.com"/>
??</S:Body>
</S:Envelope>
2,say.helloJava()响应
<S:Envelope?xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"?xmlns:wsa="http://www.w3.org/2005/08/addressing"?>
??<S:Header>
????<wsa:Action?S:mustUnderstand="1">http://mycompany.com/say/helloJava/response</wsa:Action>
????<wsa:MessageID>uuid:ba81b661-6ca8-4d34-80f6-f260d367fc48</wsa:MessageID>
????<wsa:RelatesTo>uuid:3fff81b3-2573-40b8-87f6-2b77b9e84d94</wsa:RelatesTo>
????<wsa:To>http://www.w3.org/2005/08/addressing/anonymous</wsa:To>
??</S:Header>
??<S:Body>
????<ns2:helloJavaResponse?xmlns:ns2="http://mycompany.com">
??????<return>Java</return>
????</ns2:helloJavaResponse>
??</S:Body>
</S:Envelope>
3,say.helloPython()请求
<S:Envelope?xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"?xmlns:wsa="http://www.w3.org/2005/08/addressing"?>
??<S:Header>
????<wsa:To>http://localhost:8080/say/service</wsa:To>
????<wsa:Action?S:mustUnderstand="1">http://mycompany.com/Say/helloPythonRequest</wsa:Action>
????<wsa:ReplyTo>
??????<wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
????</wsa:ReplyTo>
????<wsa:MessageID>uuid:48501be4-8f46-4cbc-bae0-f14c4d4f3ec0</wsa:MessageID>
??</S:Header>
??<S:Body>
????<ns2:helloPython?xmlns:ns2="http://mycompany.com"/>
??</S:Body>
</S:Envelope>
4,say.helloPython()响应
<S:Envelope?xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"?xmlns:wsa="http://www.w3.org/2005/08/addressing"?>
??<S:Header>
????<wsa:Action?S:mustUnderstand="1">http://mycompany.com/Say/helloPythonResponse</wsa:Action>
????<wsa:MessageID>uuid:5ccd13ef-db6d-4857-b847-954d4c763fa4</wsa:MessageID>
????<wsa:RelatesTo>uuid:48501be4-8f46-4cbc-bae0-f14c4d4f3ec0</wsa:RelatesTo>
????<wsa:To>http://www.w3.org/2005/08/addressing/anonymous</wsa:To>
??</S:Header>
??<S:Body>
????<ns2:helloPythonResponse?xmlns:ns2="http://mycompany.com">
??????<return>Python</return>
????</ns2:helloPythonResponse>
??</S:Body>
</S:Envelope>
从上面4个消息,可以看出Action,MessageID,RelatesTo,To,ReplyTo的含义。
如果需要使用NON_ANONYMOUS response的时候,我们需要使用Handler或者直接创建Dispatch的方法,来控制Header中的ReplyTo元素。wsimport所产生的portType是无法支持NON_ANONYMOUS的。
Stateful
WebService与Servlet一样,是无状态的。每个SEI只会有一个实例,不同的request会启动不同的线程,但调用的是同一个SEI实例。然而,JAXWS RI的vendor提供了有状态的机制,使得WebService也可以有状态。这样每个client可以维持一个针对自己的SEI实例。
WebService stateful是依靠WS addressing而建。若想实现Stateful,需要做到以下3点:
使用annotation?@Stateful @WebService @Addressing
声明public static StatefulWebServiceManager<T> manager。
使用另一个SEI,初始化Stateful的EndPoint,然后将EPR以W3CEndpointReference的类型暴露出去。
实例 - 服务器端
银行账户
package?com.mycompany;
import?javax.jws.WebMethod;
import?javax.jws.WebService;
import?javax.xml.ws.soap.Addressing;
import?com.sun.xml.ws.developer.Stateful;
import?com.sun.xml.ws.developer.StatefulWebServiceManager;
@Stateful
@WebService
@Addressing
public?class?Account?{
public?static?StatefulWebServiceManager<Account>?manager;
????protected?final?int?id;
????private?int?balance;
????public?Account(int?id)?{
????????this.id?=?id;
????}
????@WebMethod
????public?synchronized?void?deposit(int?amount)?{
????????balance?+=?amount;
????}
????
????@WebMethod
????public?synchronized?int?get()?{
????????return?balance;
????}
????
????@WebMethod
????public?void?logout()?{
???? manager.unexport(this);
????}
}
银行
import?javax.jws.WebMethod;
import?javax.jws.WebService;
import?javax.xml.ws.wsaddressing.W3CEndpointReference;
@WebService
public?class?Bank?{?
????@WebMethod
????public?synchronized?W3CEndpointReference?login(int?accountId)?{
????????Account?acc?=?new?Account(accountId);
????????return?Account.manager.export(acc);
????}????
}
打包部署
sun-jaxws.xml
<endpoints?xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime'?version='2.0'>
????<endpoint
????????name='account'
????????implementation='com.mycompany.Account'
????????url-pattern='/account'/>
????<endpoint
????????name='bank'
????????implementation='com.mycompany.Bank'
????????url-pattern='/service'/>
</endpoints>
在此例中,无法使用wsgen,因为WebService默认必须存在默认构造器,而Account使用了带参数的构造器。没关系,那我们就不使用wsgen了,直接将上面两个java编译好的class和sun-jaxws.xml一起打包成bank.war,然后部署就好了。
部署完成,请访问http://localhost:8080/bank/service?
实例 - 客户端
使用wsimport,对两个WSDL进行转换。
然后在App.java中调用所产生的类。
public?class?App?{
????public?static?void?main(?String[]?args?)??{
????????Bank?bank?=?new?BankService().getBankPort();
????????Account?account1?=?new?AccountService().getPort(bank.login(1),?Account.class);
????????Account?account2?=?new?AccountService().getPort(bank.login(1),?Account.class);
????????account1.deposit(100);
????????account1.deposit(300);
????????account2.deposit(200);
????????account2.deposit(100);
????????
????????System.out.println("Account?1?has?"+account1.get());
????????System.out.println("Account?2?has?"+account2.get());
????????account1.logout();
????????account2.logout();
????}
}
保持状态的原理很简单,stateful的EndPoint会为每个新的request创建新实例,SOAP Header中除了保留了Addressing的信息,还多了object的信息。这样,object可以在服务器端被找回。
<jaxws:objectId?xmlns:jaxws="http://jax-ws.dev.java.net/xml/ns/"?xmlns:ns2="http://mycompany.com/"?xmlns:ns3="http://www.w3.org/2005/08/addressing"?xmlns:wsa="http://www.w3.org/2005/08/addressing"?wsa:IsReferenceParameter="1">5198581b-bbc9-4b30-be54-6d4d969ea784</jaxws:objectId>