用JAX-WS发布RESTful Web Service,首先要创建一个对javax.xml.ws.Provider<T>接口的实现。Provider接口是对标准终端实现类的另一种动态实现办法。它与javax.xml.ws类似。Dispatch接口用在客户端。你会注意到Provider<T>是一个生成类。它能用SOAP/HTTP绑定支持Provider<javax.xml.transform.Source>和Provider<javax.xml.soap.SOAPMessage>,或者用XML/HTTP绑定支持Provider<javax.activation.DataSource>和Provider<javax.xml.transform.Source>。当创建Provider的实现时,你可以选择处理哪种形式的请求与响应消息。
我们要创建的示例Web服务非常简单,它把两个数相加然后返回结果,而我们将在Provider实现中使用Provider<Source>以及XML/HTTP绑定。首先,我们写一个AddNumbers Provider的实现来声明AddNumbers类。
public class AddNumbersImpl implements Provider {}
接下来,声明@Resource标记,它被用来在JAX-WS 运行时注射WebServiceContext到我们的AddNumbersImpl 实例中。
public class AddNumbersImpl implements Provider { @Resource protected WebServiceContext wsContext; }
下一步是实现T Provider.invoke(T request)方法。我们首先写出下面的方法声明,它是一个简单的try-catch块用来处理异常。注意,该方法以一个Source对象为请求,并返回一个Source对象作为响应。这符合我们创建的Provider类型。
public class AddNumbersImpl implements Provider { @Resource protected WebServiceContext wsContext;
public Source invoke(Source request) { try {
} catch(Exception e) { e.printStackTrace(); throw new HTTPException(500); } } }
在本例中,AddNumbers Web服务将通过从URL路径或HTTP请求中抽取要相加的数字来获得请求。请求字符串和路径字符串可以从MessageContext中获取,而它又从WebServiceContext wsContext中获得,它会被注射到我们的AddNumbers对象中。下面的代码背用来从URL中获取PATH_INFO并检查看它是否是合适的格式。
String path = (String)mc.get(MessageContext.PATH_INFO); if (path != null && path.contains("/num1") && path.contains("/num2")) { return createResultSource(path); }
createResultSource(String str)方法只从合适的被格式化的MessageContext.PATH_INFO字符串中创建Source对象。它从路径中抽取要相加的数字,然后把它们相加并调用createResultSource(int sum)方法。下面是这两个方法的源代码:
private Source createResultSource(String str) { StringTokenizer st = new StringTokenizer(str, "=&/"); String token = st.nextToken(); int number1 = Integer.parseInt(st.nextToken()); st.nextToken(); int number2 = Integer.parseInt(st.nextToken()); int sum = number1+number2; return createResultSource(sum); }
private Source createResultSource(int sum) { String body = "<ns:addNumbersResponse xmlns:ns=/"http://duke.org/"><ns:return>" +sum +"</ns:return></ns:addNumbersResponse>"; Source source = new StreamSource( new ByteArrayInputStream(body.getBytes())); return source; }
于是,我们的示例invoke方法就会是下面这个样子。
public Source invoke(Source source) { try { MessageContext mc = wsContext.getMessageContext(); // check for a PATH_INFO request String path = (String)mc.get(MessageContext.PATH_INFO); if (path != null && path.contains("/num1") && path.contains("/num2")) { return createResultSource(path); } throw new HTTPException(404); } catch(Exception e) { e.printStackTrace(); throw new HTTPException(500); } }
你会发现,如果MessageContext.PATH_INFO没有找到,该示例会抛出HTTPException(404)异常。我们的示例终端并不抛出该异常,而是能为方法参数检查请求字符串。例如MessageContext.PATH_INFO,请求字符串可以用下面的代码从MessageContext中获得。
String query = (String)mc.get(MessageContext.QUERY_STRING);
于是,我们可以把请求字符串传递到createResultSource(String str)方法中来解析参数,如果要为我们的示例部署一个servlet容器也可以使用标准ServletRequest对象来抽取请求字符串。而如果我们想部署一个基于Java SE的终端,我们就必须使用MessageContext.QUERY_STRING。
下面的代码获取了num1和num2的值,相加后调用createResultSource(int sum)方法来创建Source对象,它将从invoke方法中被返回。
ServletRequest req = (ServletRequest)mc.get(MessageContext.SERVLET_REQUEST); int num1 = Integer.parseInt(req.getParameter("num1")); int num2 = Integer.parseInt(req.getParameter("num2")); return createResultSource(num1+num2);
至此,我们的AddNumbers类应该如下所示:
public class AddNumbersImpl implements Provider {
@Resource protected WebServiceContext wsContext;
public Source invoke(Source source) { try { MessageContext mc = wsContext.getMessageContext(); // check for a PATH_INFO request String path = (String)mc.get(MessageContext.PATH_INFO); if (path != null && path.contains("/num1") && path.contains("/num2")) { return createResultSource(path); } String query = (String)mc.get(MessageContext.QUERY_STRING); System.out.println("Query String = "+query); ServletRequest req = (ServletRequest)mc.get(MessageContext.SERVLET_REQUEST); int num1 = Integer.parseInt(req.getParameter("num1")); int num2 = Integer.parseInt(req.getParameter("num2")); return createResultSource(num1+num2); } catch(Exception e) { e.printStackTrace(); throw new HTTPException(500); } } private Source createResultSource(String str) { StringTokenizer st = new StringTokenizer(str, "=&/"); String token = st.nextToken(); int number1 = Integer.parseInt(st.nextToken()); st.nextToken(); int number2 = Integer.parseInt(st.nextToken()); int sum = number1+number2; return createResultSource(sum); } private Source createResultSource(int sum) { String body = "<ns:addNumbersResponse xmlns:ns=/"http://duke.org/"><ns:return>" +sum +"</ns:return></ns:addNumbersResponse>"; Source source = new StreamSource( new ByteArrayInputStream(body.getBytes())); return source; } }
要完成AddNumbers,我们需要声明一些标记来说明JAX-WS运行时如何使用这个类。@WebServiceProvider表明该类是一个基于Provider的终端而不是一个需要用@WebService指明的服务终端实现类。最后需要我们添加的标记是@BindingType(value=HTTPBinding.HTTP_BINDING)。该标记指明了AddNumbers终端将用HTTPBinding.HTTP_BINDING而不是SOAPBinding.SOAP11HTTP_BINDING或者SOAPBinding.SOAP12HTTP_BINDING来绑定。下面是最终该类的代码:
@WebServiceProvider @BindingType(value=HTTPBinding.HTTP_BINDING) public class AddNumbersImpl implements Provider {
@Resource protected WebServiceContext wsContext;
public Source invoke(Source source) { try { MessageContext mc = wsContext.getMessageContext(); // check for a PATH_INFO request String path = (String)mc.get(MessageContext.PATH_INFO); if (path != null && path.contains("/num1") && path.contains("/num2")) { return createResultSource(path); } String query = (String)mc.get(MessageContext.QUERY_STRING); System.out.println("Query String = "+query); ServletRequest req = (ServletRequest)mc.get(MessageContext.SERVLET_REQUEST); int num1 = Integer.parseInt(req.getParameter("num1")); int num2 = Integer.parseInt(req.getParameter("num2")); return createResultSource(num1+num2); } catch(Exception e) { e.printStackTrace(); throw new HTTPException(500); } } private Source createResultSource(String str) { StringTokenizer st = new StringTokenizer(str, "=&/"); String token = st.nextToken(); int number1 = Integer.parseInt(st.nextToken()); st.nextToken(); int number2 = Integer.parseInt(st.nextToken()); int sum = number1+number2; return createResultSource(sum); } private Source createResultSource(int sum) { String body = "<ns:addNumbersResponse xmlns:ns=/"http://duke.org/"><ns:return>" +sum +"</ns:return></ns:addNumbersResponse>"; Source source = new StreamSource( new ByteArrayInputStream(body.getBytes())); return source; } }
要在运行了JAX-WS RI的servlet容器上部署我们的终端,我们需要创建一个WAR文件。例如,我们把WAR文件命名为jaxws-restful.war。该文件需要一个非常简单的web.xml文件来配置JAX-WS Ri servlet。代码如下:
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"> <listener> <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener </listener> <servlet> <servlet-name>restful-addnumbers</servlet-name> <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>restful-addnumbers</servlet-name> <url-pattern>/addnumbers/*</url-pattern> </servlet-mapping> <session-config> <session-timeout>60</session-timeout> </session-config> </web-app>
接着,我们添加一个 sun-jaxws.xml部署描述符到该WAR文件。下面是示例:
<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0">
<endpoint name="restful-addnumbers" implementation="restful.server.AddNumbersImpl" wsdl="WEB-INF/wsdl/AddNumbers.wsdl" url-pattern="/addnumbers/*" /> </endpoints>
你会注意到在web.xml和sun-jaxws.xml中有<url-pattern>/addnumbers/*</url-pattern>元素和url-pattern="/addnumbers/*"属性,于是我们能够在路径中匹配任何附加的参数。(例如,.../num1/10/num2/20)
你还会从sun-jaxws.xml部署描述符中注意到指定的一个wsdl文件。它很重要的告诉JAX-WS RI不要为该终端生成一个WSDL。下面是一个简单的AddNumbers.wsdl的内容。
<?xml version="1.0" encoding="UTF-8"?> <definitions> </definitions>
一旦部署了AddNumbers终端,你就能在浏览器中输入下面的URL来测试它了。(根据你配置的不同可能会有一点点不同)
http://localhost:8080/jaxws-restful/addnumbers/num1/10/num2/20 Should return the following: <ns:addNumbersResponse xmlns:ns="http://duke.org"> <ns:return>30</ns:return> </ns:addNumbersResponse>
http://localhost:8080/jaxws-restful/addnumbers?num1=10&num2=50 Should return the following: <ns:addNumbersResponse xmlns:ns="http://duke.org"> <ns:return>60</ns:return> </ns:addNumbersResponse>