一、消息寻址
WS-Addressing是将消息路由数据包含在SOAP头中的一种标准方法。利用WS-Addressing的消息可以在标准化的SOAP头中包含自己的包含发送元数据,而不是依赖于网络层传输来传送路由信息。通过在标准的SOAP头中(wsa:ReplyTo)指定应答消息应该发送到哪里的端点引用,WS-Addressing可以支持异步交互方式。 服务提供者使用另一个连接,将应答消息发送给wsa:ReplyTo所指定的端点。这就将SOAP请求/应答消息的交互与HTTP请求/应答协议分离,这样,跨越任意时间的长时间运行的交互成为可能。
通过这种方式,当服务器端需要较长时间来处理业务的时候,不需要一直保持与客户端的连接,当处理完毕之后,服务器将结果返回给指定的客户端。这类似于异步的交互。
二、基于cxf的实现
编写wsdl文件,存放于工程的src目录下
<?xml version="1.0" encoding="UTF-8"?> <wsdl:definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://apache.org/hello_world_soap_http" xmlns:x1="http://apache.org/hello_world_soap_http/types" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="HelloWorld" targetNamespace="http://apache.org/hello_world_soap_http"> <wsdl:types> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://apache.org/hello_world_soap_http/types" elementFormDefault="qualified"> <element name="sayHi"> <complexType /> </element> <element name="sayHiResponse"> <complexType> <sequence> <element name="responseType" type="xsd:string" /> </sequence> </complexType> </element> <element name="greetMe"> <complexType> <sequence> <element name="requestType" type="xsd:string" /> </sequence> </complexType> </element> <element name="greetMeResponse"> <complexType> <sequence> <element name="responseType" type="xsd:string" /> </sequence> </complexType> </element> <element name="greetMeOneWay"> <complexType> <sequence> <element name="requestType" type="xsd:string" /> </sequence> </complexType> </element> <element name="pingMe"> <complexType /> </element> <element name="pingMeResponse"> <complexType /> </element> <element name="faultDetail"> <complexType> <sequence> <element name="minor" type="xsd:short" /> <element name="major" type="xsd:short" /> </sequence> </complexType> </element> </schema> </wsdl:types> <wsdl:message name="sayHiRequest"> <wsdl:part element="x1:sayHi" name="in" /> </wsdl:message> <wsdl:message name="sayHiResponse"> <wsdl:part element="x1:sayHiResponse" name="out" /> </wsdl:message> <wsdl:message name="greetMeRequest"> <wsdl:part element="x1:greetMe" name="in" /> </wsdl:message> <wsdl:message name="greetMeResponse"> <wsdl:part element="x1:greetMeResponse" name="out" /> </wsdl:message> <wsdl:message name="greetMeOneWayRequest"> <wsdl:part element="x1:greetMeOneWay" name="in" /> </wsdl:message> <wsdl:message name="pingMeRequest"> <wsdl:part name="in" element="x1:pingMe" /> </wsdl:message> <wsdl:message name="pingMeResponse"> <wsdl:part name="out" element="x1:pingMeResponse" /> </wsdl:message> <wsdl:message name="pingMeFault"> <wsdl:part name="faultDetail" element="x1:faultDetail" /> </wsdl:message> <wsdl:portType name="Greeter"> <wsdl:operation name="sayHi"> <wsdl:input message="tns:sayHiRequest" name="sayHiRequest" /> <wsdl:output message="tns:sayHiResponse" name="sayHiResponse" /> </wsdl:operation> <wsdl:operation name="greetMe"> <wsdl:input message="tns:greetMeRequest" name="greetMeRequest" /> <wsdl:output message="tns:greetMeResponse" name="greetMeResponse" /> </wsdl:operation> <wsdl:operation name="greetMeOneWay"> <wsdl:input message="tns:greetMeOneWayRequest" name="greetMeOneWayRequest" /> </wsdl:operation> <wsdl:operation name="pingMe"> <wsdl:input name="pingMeRequest" message="tns:pingMeRequest" /> <wsdl:output name="pingMeResponse" message="tns:pingMeResponse" /> <wsdl:fault name="pingMeFault" message="tns:pingMeFault" /> </wsdl:operation> </wsdl:portType> <wsdl:binding name="Greeter_SOAPBinding" type="tns:Greeter"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="sayHi"> <soap:operation soapAction="" style="document" /> <wsdl:input name="sayHiRequest"> <soap:body use="literal" /> </wsdl:input> <wsdl:output name="sayHiResponse"> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> <wsdl:operation name="greetMe"> <soap:operation soapAction="" style="document" /> <wsdl:input name="greetMeRequest"> <soap:body use="literal" /> </wsdl:input> <wsdl:output name="greetMeResponse"> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> <wsdl:operation name="greetMeOneWay"> <soap:operation soapAction="" style="document" /> <wsdl:input name="greetMeOneWayRequest"> <soap:body use="literal" /> </wsdl:input> </wsdl:operation> <wsdl:operation name="pingMe"> <soap:operation style="document" /> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> <wsdl:fault name="pingMeFault"> <soap:fault name="pingMeFault" use="literal" /> </wsdl:fault> </wsdl:operation> </wsdl:binding> <!-- SOAPService 的访问地址, 与server中的发布地址一致 --> <wsdl:service name="SOAPService"> <wsdl:port binding="tns:Greeter_SOAPBinding" name="SoapPort"> <soap:address location="http://localhost:9000/SoapContext/SoapPort" /> <wswa:UsingAddressing xmlns:wswa="http://www.w3.org/2005/02/addressing/wsdl" /> </wsdl:port> </wsdl:service> </wsdl:definitions>
服务实现类GreeterImpl:
package demo.ws_addressing.server; import java.util.logging.Logger; import javax.jws.WebService; import org.apache.hello_world_soap_http.Greeter; import org.apache.hello_world_soap_http.PingMeFault; import org.apache.hello_world_soap_http.types.FaultDetail; @WebService(name = "SoapPort", portName = "SoapPort", serviceName = "SOAPService", targetNamespace = "http://apache.org/hello_world_soap_http", wsdlLocation = "file:./src/hello_world_addr.wsdl") // wsdlLocation 指定wsdl文件的地址 public class GreeterImpl implements Greeter { private static final Logger LOG = Logger.getLogger(GreeterImpl.class .getPackage().getName()); public String greetMe(String me) { LOG.info("Executing operation greetMe"); System.out.println("Executing operation greetMe"); System.out.println("Message received: " + me + " "); return "Hello " + me; } public void greetMeOneWay(String me) { LOG.info("Executing operation greetMeOneWay"); System.out.println("Executing operation greetMeOneWay "); System.out.println("Hello there " + me); } public String sayHi() { LOG.info("Executing operation sayHi"); System.out.println("Executing operation sayHi "); return "Bonjour"; } public void pingMe() throws PingMeFault { FaultDetail faultDetail = new FaultDetail(); faultDetail.setMajor((short) 2); faultDetail.setMinor((short) 1); LOG.info("Executing operation pingMe, throwing PingMeFault exception"); System.out .println("Executing operation pingMe, throwing PingMeFault exception "); throw new PingMeFault("PingMeFault raised by server", faultDetail); } }
服务器端配置文件server.xml,存放在demo.ws_addressing.server包下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:cxf="http://cxf.apache.org/core" xmlns:wsa="http://cxf.apache.org/ws/addressing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <cxf:bus> <cxf:features> <wsa:addressing /> </cxf:features> </cxf:bus> </beans>
服务端启动类Server:
package demo.ws_addressing.server; import java.net.URL; import javax.xml.ws.Endpoint; import org.apache.cxf.Bus; import org.apache.cxf.BusFactory; import org.apache.cxf.bus.spring.SpringBusFactory; public class Server { protected Server() throws Exception { System.out.println("Starting Server"); SpringBusFactory bf = new SpringBusFactory(); URL busFile = Server.class.getResource("server.xml"); Bus bus = bf.createBus(busFile.toString()); BusFactory.setDefaultBus(bus); Object implementor = new GreeterImpl(); String address = "http://localhost:9000/SoapContext/SoapPort"; // 发布服务地址,与wsdl中的访问地址一致 Endpoint.publish(address, implementor); } public static void main(String args[]) throws Exception { new Server(); System.out.println("Server ready..."); Thread.sleep(5 * 60 * 1000); System.out.println("Server exiting"); System.exit(0); } }
客户端配置文件client.xml存放在demo.ws_addressing.client包下:
<?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:cxf="http://cxf.apache.org/core" xmlns:wsa="http://cxf.apache.org/ws/addressing" xmlns:http="http://cxf.apache.org/transports/http/configuration" xsi:schemaLocation="http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 表示服务器端处理完成后,将结果返回给"http://localhost:9990/decoupled_endpoint"。 这个地址。如果直接返回给自己,则可将下面注释 --> <http:conduit name="{http://apache.org/hello_world_soap_http}SoapPort.http-conduit"> <http:client DecoupledEndpoint="http://localhost:9990/decoupled_endpoint" /> </http:conduit> <cxf:bus> <cxf:features> <wsa:addressing /> </cxf:features> </cxf:bus> </beans>
客户端访问类Client:
package demo.ws_addressing.client; import static org.apache.cxf.ws.addressing.JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES; import java.lang.reflect.UndeclaredThrowableException; import java.net.URL; import java.util.Map; import javax.xml.namespace.QName; import javax.xml.ws.BindingProvider; import org.apache.cxf.Bus; import org.apache.cxf.BusFactory; import org.apache.cxf.bus.spring.SpringBusFactory; import org.apache.cxf.ws.addressing.AddressingProperties; import org.apache.cxf.ws.addressing.AttributedURIType; import org.apache.cxf.ws.addressing.ObjectFactory; import org.apache.hello_world_soap_http.Greeter; import org.apache.hello_world_soap_http.PingMeFault; import org.apache.hello_world_soap_http.SOAPService; public final class Client { private static final QName SERVICE_NAME = new QName( "http://apache.org/hello_world_soap_http", "SOAPService"); private static final ObjectFactory WSA_OBJECT_FACTORY = new ObjectFactory(); private static final String USER_NAME = System.getProperty("user.name"); private Client() { } public static void main(String args[]) throws Exception { try { URL wsdlURL = Client.class.getClassLoader().getResource( "hello_world_addr.wsdl");//加载wsdl文件 SpringBusFactory bf = new SpringBusFactory(); URL busFile = Client.class.getResource("client.xml"); Bus bus = bf.createBus(busFile.toString()); BusFactory.setDefaultBus(bus); SOAPService service = new SOAPService(wsdlURL, SERVICE_NAME); Greeter port = service.getSoapPort(); implicitPropagation(port); // explicitPropagation(port); // implicitPropagation(port); } catch (UndeclaredThrowableException ex) { ex.getUndeclaredThrowable().printStackTrace(); } catch (Throwable ex) { ex.printStackTrace(); } finally { System.exit(0); } } /** * A series of invocations with implicitly propogated Message Addressing * Properties. */ private static void implicitPropagation(Greeter port) { System.out.println(); System.out.println("Implicit MessageAddressingProperties propagation"); System.out.println("------------------------------------------------"); System.out.println("Invoking sayHi..."); String resp = port.sayHi(); System.out.println("Server responded with: " + resp + " "); System.out.println("Invoking greetMe..."); resp = port.greetMe(USER_NAME); System.out.println("Server responded with: " + resp + " "); System.out.println("Invoking greetMeOneWay..."); port.greetMeOneWay(USER_NAME); System.out.println("No response from server as method is OneWay "); try { System.out.println("Invoking pingMe, expecting exception..."); port.pingMe(); } catch (PingMeFault ex) { System.out.println("Expected exception occurred: " + ex); } } private static AddressingProperties createMaps() { // get Message Addressing Properties instance AddressingProperties maps = new AddressingProperties(); // set MessageID property AttributedURIType messageID = WSA_OBJECT_FACTORY .createAttributedURIType(); messageID.setValue("urn:uuid:" + System.currentTimeMillis()); maps.setMessageID(messageID); return maps; } /** * A series of invocations with explicitly propogated Message Addressing * Properties. */ private static void explicitPropagation(Greeter port) { System.out.println(); System.out.println("Explicit MessageAddressingProperties propagation"); System.out.println("------------------------------------------------"); // associate MAPs with request context Map<String, Object> requestContext = ((BindingProvider) port) .getRequestContext(); requestContext.put(CLIENT_ADDRESSING_PROPERTIES, createMaps()); System.out.println("Invoking sayHi..."); String resp = port.sayHi(); System.out.println("Server responded with: " + resp + " "); // set the RelatesTo property to the initial message ID, so that // the series of invocations are explicitly related // RelatesToType relatesTo = WSA_OBJECT_FACTORY.createRelatesToType(); // relatesTo.setValue(messageID.getValue()); // maps.setRelatesTo(relatesTo); System.out.println("Invoking greetMe..."); requestContext.put(CLIENT_ADDRESSING_PROPERTIES, createMaps()); resp = port.greetMe(USER_NAME); System.out.println("Server responded with: " + resp + " "); System.out.println("Invoking greetMeOneWay..."); requestContext.put(CLIENT_ADDRESSING_PROPERTIES, createMaps()); port.greetMeOneWay(USER_NAME); System.out.println("No response from server as method is OneWay "); // disassociate MAPs from request context requestContext.remove(CLIENT_ADDRESSING_PROPERTIES); } }
分别运行Server类和Client类即可正常访问。通过tcpMon监听结果如下:
1、当client中没有指定<http:conduit>元素时:
请求:
响应
请求被处理后,结果返回给了原来的地址。
2、当client中指定了<http:conduit>元素时:
请求
该soap请求<soap:Header>中指定了处理完成后,将结果返回给特定的地址。
响应
响应为202,没有响应体。服务器已接受请求,但尚未处理。正如它可能被拒绝一样,最终该请求可能会也可能不会被执行。服务器接收到请求后,将处理后的结果转发给"http://localhost:9990/decoupled_endpoint" 这个地址。