在之前的文章中,涉及到了WebService的搭建。所有的EndPoint均是高度面向对象,面向逻辑了。Server与Client之间交互的消息,均由JAXB转为JAVA类型。如果想对消息的原始数据进行修改,可以使用Handler Chain。
然而,JAXWS也提供了另一种编程方式,Provider和Dispatch,让我们抛开高度抽象的EndPoint和JAXB,抛开了工具wsgen,wsimport,直接面向消息编程。
Provider
介绍
Provider是server端直接面向消息编程的接口。我们先看下Provider接口中的方法:
package javax.xml.ws.Providerpublic interface Provider
{ 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
创建一个java web project,取名叫LibraryProvider。然后将之前图书馆系统的WSDL文件复制到新项目的WEB-INF目录下面。
创建Provider
创建java类,LibraryProvider:
@ServiceMode(value=Service.Mode.PAYLOAD)@WebServiceProvider()public class LibraryProvider implements Provider { private static int currentId = 0; private static Map
books= new HashMap
(); 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 = "
" +currentId +"
"; 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 = "
" +b.toString() +"
"; Source source = new StreamSource(new ByteArrayInputStream(body.getBytes())); return source; } private Source deleteBook(Node root) { // 略 }}
sun-jaxws.xml
在WEB-INF下创建sun-jaxws.xml文件:
打包部署运行
将project导出为war包library.war,部署到tomcat下。访问
然后使用wsimport,产生client端的代码。对client端进行调用,查看结果。
异步
关于提供服务端异步执行功能的AsyncProvider,我会另写一篇文章介绍。
Dispatch
介绍
接口Dispatch和Provider相对,用于client端。其功能与Provider一样,提供了面向消息的编程方法。先看下Dispatch提供了哪些方法:
package javax.xml.ws;import java.util.concurrent.Future;public interface Dispatch
extends BindingProvider { public T invoke(T msg); public Response
invokeAsync(T msg); public Future
invokeAsync(T msg, AsyncHandler
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); 处理单向消息。
异步
关于异步,会在另一篇文章中同意描述。
创建client
由于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 sourceDispatch = service.createDispatch(portName, Source.class, Service.Mode.PAYLOAD); // add book String body = "
java
xpbug
"; 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; }}
不需要任何额外的命令,依赖,以及部署,便可直接运行上面的程序。