在之前的文章中,涉及到了WebService的搭建。所有的EndPoint均是高度面向对象,面向逻辑了。Server与Client之间交互的消息,均由JAXB转为JAVA类型。如果想对消息的原始数据进行修改,可以使用Handler Chain。
然而,JAXWS也提供了另一种编程方式,Provider和Dispatch,让我们抛开高度抽象的EndPoint和JAXB,抛开了工具wsgen,wsimport,直接面向消息编程。
Provider是server端直接面向消息编程的接口。我们先看下Provider接口中的方法:
package javax.xml.ws.Provider public interface Provider<T> { public T invoke(T request); }
服务端EndPoint必须实现此接口。Provider就像是HttpServlet,invoke()就像是service()。invoke方法参数是接收的原始消息,返回值是返回的消息。
T是对消息封装的一种泛型。结合Provider的Annotation @ServiceMode,它可以为三种封装类型:
javax.xml.transform.Source 将消息中的Payload封装为XML类型Source。适用于@ServiceMode(value=Service.Mode.PAYLOAD)。
javax.xml.soap.SOAPMessage 将消息整体封装为SOAPMessage。适用于@ServiceMode(value=Service.Mode.MESSAGE)。
javax.activation.DataSource
Provider无法使用wsgen命令生成WSDL文件,所以,只能先用WebService Interface生成WSDL,然后再编写Provider。
下面的例子是基于之前的图书馆系统而修改的,所以复用之前图书馆系统的WSDL文件。
创建一个java web project,取名叫LibraryProvider。然后将之前图书馆系统的WSDL文件复制到新项目的WEB-INF目录下面。
创建java类,LibraryProvider:
@ServiceMode(value=Service.Mode.PAYLOAD) @WebServiceProvider() public class LibraryProvider implements Provider<Source> { private static int currentId = 0; private static Map<Integer, Book> books= new HashMap<Integer, Book>(); private static class Book { public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } private int id; private String name; private String author; } @Override public Source invoke(Source request) { try { DOMResult dom = new DOMResult(); Transformer trans = TransformerFactory.newInstance().newTransformer(); trans.transform(request, dom); Node node = dom.getNode(); Node root = node.getFirstChild(); String operation = root.getLocalName(); if ("addRawBook".equals(operation)) { return addRawBook(root); } if ("getRawBook".equals(operation)) { return getRawBook(root); } if ("deleteBook".equals(operation)) { return deleteBook(root); } return request; } catch(Exception e) { e.printStackTrace(); throw new RuntimeException("Error in provider endpoint", e); } } private Source addRawBook(Node root) { String name = root.getChildNodes().item(0).getChildNodes().item(0).getNodeValue(); String author = root.getChildNodes().item(1).getChildNodes().item(0).getNodeValue(); Book b = new Book(); b.setName(name); b.setAuthor(author); b.setId(++currentId); books.put(b.getId(), b); String body = "<ns2:addRawBookResponse xmlns:ns2=\"http://library.mycompany.com\"><id>" +currentId +"</id></ns2:addRawBookResponse>"; Source source = new StreamSource(new ByteArrayInputStream(body.getBytes())); return source; } private Source getRawBook(Node root) { String idString = root.getChildNodes().item(0).getChildNodes().item(0).getNodeValue(); Book b = books.get(Integer.parseInt(idString)); String body = "<ns2:getRawBookResponse xmlns:ns2=\"http://library.mycompany.com\"><rawBook>" +b.toString() +"</rawBook></ns2:getRawBookResponse>"; Source source = new StreamSource(new ByteArrayInputStream(body.getBytes())); return source; } private Source deleteBook(Node root) { // 略 } }
在WEB-INF下创建sun-jaxws.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0"> <endpoint name="provider" implementation="com.mycompany.library.LibraryProvider" wsdl="WEB-INF/wsdl/LibraryService.wsdl" service="{http://library.mycompany.com}LibraryService" port="{http://library.mycompany.com}LibraryPort" url-pattern="/service" /> </endpoints>
将project导出为war包library.war,部署到tomcat下。访问http://127.0.0.1:8080/library/service?wsdl
然后使用wsimport,产生client端的代码。对client端进行调用,查看结果。
关于提供服务端异步执行功能的AsyncProvider,我会另写一篇文章介绍。
接口Dispatch和Provider相对,用于client端。其功能与Provider一样,提供了面向消息的编程方法。先看下Dispatch提供了哪些方法:
package javax.xml.ws; import java.util.concurrent.Future; public interface Dispatch<T> extends BindingProvider { public T invoke(T msg); public Response<T> invokeAsync(T msg); public Future<?> invokeAsync(T msg, AsyncHandler<T> handler); public void invokeOneWay(T msg); }
T泛型与Provider中的T一样。而方法invoke与Provider中的invoke也相同。Dispatch还提供了另外3个方法:
Response<T> invokeAsync(T msg); 为异步poll的方式, Response是一个Future。
Future<?> invokeAsync(T msg, AsyncHandler<T> handler); 为异步callback方式。Handler会在另一个线程中处理返回的值。
void invokeOneWay(T msg); 处理单向消息。
关于异步,会在另一篇文章中同意描述。
由于Dispatch摆脱了JAXB,所以不需要调用wsimport命令。只需要一个简单的client程序,就可以调用服务器端的service。我们以图书馆webservice为例。
public class Client { public static void main(String[] a) throws Exception { URL url = new URL("http://127.0.0.1:8080/library/service?wsdl"); final QName serviceQName = new QName("http://library.mycompany.com", "LibraryService"); Service service = Service.create(url, serviceQName); // 创建一个新的port,也可以使用WSDL中已有的port。如果WSDL中已经有此port,则不需要再进行binding。 QName portName = new QName("http://library.mycompany.com", "RandomPort"); service.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING,"http://127.0.0.1:8080/library/service"); // 为port创建一个dispatch。所有流向port的消息都将由dispatch处理。 Dispatch<Source> sourceDispatch = service.createDispatch(portName, Source.class, Service.Mode.PAYLOAD); // add book String body = "<ns2:addRawBook xmlns:ns2=\"http://library.mycompany.com\"><name>java</name><author>xpbug</author></ns2:addRawBook>"; Source result = sourceDispatch.invoke(new StreamSource(new StringReader(body))); System.out.println(sourceToXMLString(result)); } private static String sourceToXMLString(Source result) { String xmlResult = null; try { TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); OutputStream out = new ByteArrayOutputStream(); StreamResult streamResult = new StreamResult(); streamResult.setOutputStream(out); transformer.transform(result, streamResult); xmlResult = streamResult.getOutputStream().toString(); } catch (TransformerException e) { e.printStackTrace(); } return xmlResult; } }
不需要任何额外的命令,依赖,以及部署,便可直接运行上面的程序。