Web Services作为一个分布式的技术平台,JAX-WS和JWS为其所定义的编程模型涵盖了服务器端和客户端两部分。Java环境中的WS客户端分为如下两类:
独立的Java应用,桌面应用和applet可归为此类
JavaEE容器托管的的应用模块,包括容器托管的客户端、Web、EJB和JCA应用
根据JWS规范规定,Java客户端访问Web服务的方式有如下几类:
通过工具类库生成的静态客户端代理
在XML、SOAP或更低层面直接操作请求和响应消息的编程API——SAAJ、JAX-WS
客户端异步Web服务调用机制——轮询或回调
JavaEE服务器托管的Web服务客户端代理——@WebServiceRef
这种分类依据是调用方式和抽象层次的不同,并不是一个严格泾渭分明、界限清晰的分类,某些类别在底层可能就使用了另一类别中的技术。
另外,客户端类别和Web服务使用方式之间并没有确定的对应关系,除了容器托管的服务客户端代理一般只能用于容器托管的应用模块,其他的方式两类客户端都可以使用。
下面将针对前面所的Hello示例Web服务(JAX-WS POJO版本,Stateless EJB版本使用方式类似)分别使用上述技术开发一个示例客户端:
开发步骤:
在已经设置好JDK环境变量的前提下,打开命令行窗口,使用JDK自带的工具wsimport,指定Web服务URL地址,生成客户端代理。如下图:
进入目录查看,发现在包com.apusic.ws.client.proxy中产生了下图所示的类结构:
说明(各Java类的角色与作用):
package-info:用于定制包级别的Java-WSDL映射
HelloPOJOService:服务的客户端视图,客户端调用的入口
HelloSEI:端口的客户端编程接口
Hello、HelloResponse:操作调用的请求和响应消息对象,遵循JAXB映射
ObjectFactory:上述JAXB对象的工厂类
将上述代理加入类路径,即可开始客户端编写,最终使用静态客户端代理的WS客户端代码如下:
package com.apusic.ws.client; import com.apusic.ws.client.proxy.HelloPOJOService; import com.apusic.ws.client.proxy.HelloSEI; public class StaticProxyClient { public static void main(String[] args) { HelloPOJOService service = new HelloPOJOService(); HelloSEI port = service.getHelloPOJOPort(); String response1 = port.hello("static-proxy-client"); // response would be "hello static-proxy-client from jaxws pojo endpoint" } }
代码演示了通过静态代理方式进行同步WS调用的步骤,可以看出,通过JAX-WS API屏蔽了底层消息封装、协议绑定和网络通信的细节,静态代理的方式无疑是最简单易用的。在示例中,手动生成代理类的方式加重了些许工作负担,但这项工作完全可以通过开发工具自动完成;另外,有些框架和工具中还提供了动态客户端的支持(类似于Java中的反射调用),完全不需要显式生成任何类。
通过JAX-WS所提供的编程接口,可直接在XML消息层面上操作请求和响应消息进行WS调用,此时无需代理类的参与,下面的示例代码演示其中的一种方式:
package com.apusic.ws.client; import java.net.MalformedURLException; import java.net.URL; import java.util.concurrent.ExecutionException; import javax.xml.namespace.QName; import javax.xml.soap.MessageFactory; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPMessage; import javax.xml.ws.Dispatch; import javax.xml.ws.Response; import javax.xml.ws.Service; import org.w3c.dom.DOMException; public class XMLMessageClient { public static void main(String[] args) throws MalformedURLException, SOAPException, DOMException, InterruptedException, ExecutionException { String namespaceUri = "http://sei.endpoint.ws.apusic.com/"; Service service = Service.create(new URL("http://localhost:6888/hello/HelloPOJOService?wsdl"), new QName(namespaceUri, "HelloPOJOService")); Dispatch<SOAPMessage> dispatch = service.createDispatch(new QName(namespaceUri, "HelloPOJOPort"), SOAPMessage.class, Service.Mode.MESSAGE); MessageFactory msgFactory = MessageFactory.newInstance(); SOAPMessage request = msgFactory.createMessage(); request.getSOAPBody().addBodyElement(new QName(namespaceUri, "hello", "tns")).addChildElement(new QName("arg0")).addTextNode("dispatch-soap-message"); request.saveChanges(); SOAPMessage response1 = dispatch.invoke(request); String responseMsg1 = ((SOAPElement) ((SOAPElement) response1.getSOAPBody().getChildElements(new QName(namespaceUri, "helloResponse")).next()) .getChildElements(new QName("return")).next()).getFirstChild().getTextContent(); Response<SOAPMessage> response2 = dispatch.invokeAsync(request); String responseMsg2=null; while(response2.isDone()) responseMsg2=((SOAPElement) ((SOAPElement) response2.get().getSOAPBody().getChildElements(new QName(namespaceUri, "helloResponse")).next()) .getChildElements(new QName("return")).next()).getFirstChild().getTextContent(); //responseMsg1 and responseMsg2 should both be "hello dispatch-soap-message from jaxws pojo endpoint" } }
上面的代码同时演示了同步与异步(轮询)动态调用的情况,我们并没有对客户端代码进行任何修改,由此可以确认JAX-WS通过客户端编程模型支持异步WS调用。
该类别的WS调用方式只适用与JavaEE服务器(如Apusic应用服务器)托管下的应用组件,这些组件既包括传统的Servlet、EJB等,甚至可以另一个需要使用其他Web服务的端口组件,这样可以通过一个集中的入口整合各种资源为客户提供一致的服务。
开发一个简单的Servlet,其中通过@WebServiceRef注解注入Hello Web服务客户端。值得注意的是,此种方式需要使用到前面通过工具生成的服务客户端视图和SEI接口,由此也导致了@WebServiceRef注解的两种使用方式,本示例只演示其中的一种,代码如下:
package com.apusic.ws.client.servlet; import java.io.IOException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.ws.WebServiceRef; import com.apusic.ws.client.proxy.HelloPOJOService; import com.apusic.ws.client.proxy.HelloSEI; public class ClientServlet extends HttpServlet { @WebServiceRef(name = "helloService", value = HelloPOJOService.class) private HelloSEI service; public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { process(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { process(request, response); } private void process(HttpServletRequest request, HttpServletResponse response) throws IOException { String wsMessage = service.hello("web-service-ref"); response.getWriter().print(wsMessage); } }
应用的web.xml配置文件内容如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <servlet> <servlet-name>ClientServlet</servlet-name> <servlet-class>com.apusic.ws.client.servlet.ClientServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ClientServlet</servlet-name> <url-pattern>/client</url-pattern> </servlet-mapping> <service-ref> <service-ref-name>helloService</service-ref-name> <service-interface>com.apusic.ws.client.proxy.HelloPOJOService</service-interface> <service-ref-type>com.apusic.ws.client.proxy.HelloSEI</service-ref-type> <wsdl-file>http://localhost:6888/hello/HelloPOJOService?wsdl</wsdl-file> </service-ref> </web-app>
当通过浏览器请求Servlet时会看到响应hello web-service-ref from jaxws pojo endpoint,表明Hello Web服务调用成功。
可以看到,采用容器注入方式的优势在于能够将使用代码和配置文件分离,这样Web服务被部署到其他位置,将只需要修改配置文件,而无需改动应用代码。
在众多的Web服务客户端调用方式中,本文只选取了其中典型的几个进行讲解,但这只是初涉皮毛,其他注意事项和调用方式请参考JWS和JAX-WS规范以及通用于JavaEE环境的各种教程。