JAX-WS(JWS)发布WebService
WebService历来都很受重视,特别是Java阵营,WebService框架和技术层出不穷。知名的XFile(新的如CXF)、Axis1、Axis2等。
而Sun公司也不甘落后,从早期的JAX-RPC到现在成熟的、支持RPC调用与消息传递的JAX-WS都经过了市场的考验,十分成熟,而且使用JAX-WS开发WebService的收益是很大的,它是轻量级的。
一、WebService的开发方法
使用Java开发WebService时可以使用以下两种开发手段
1、 使用JDK开发(1.6及以上版本)
2、使用第三方组件,如CXF框架开发
二、使用JDK开发WebService
JAX-WS 2.0 有两种开发过程:自顶向下和自底向上。自顶向下方式指通过一个 WSDL 文件来创建Web Service,自底向上是从 Java 类出发创建 Web Service。两种开发过程最终形成的文件包括:
1.SEI(Service Endpoint Interface 发布的服务接口)。一个SEI对应WSDL中WebService的一个port,在Java中是一个Java接口。
2.SEI实现类。
3.WSDL和XSD文件。
我们使用JAX-WS开发WebService只需要很简单的几个步骤:写接口和实现=>发布=>生成客户端(测试或使用)。
2.1、开发WebService服务器端
而在开发阶段我们也不需要导入外部jar包,因为这些api都是现成的。首先是接口的编写(接口中只需要把类注明为@WebService,把要暴露给客户端的方法注明为@WebMethod即可,其余如@WebResult、@WebParam等都不是必要的,而客户端和服务端的通信用RPC和Message-Oriented两种,区别和配置以后再说):
注解说明:
@WebService 注释在了Class之上,这告诉了JAXWS,此类为Webservice。@WebService注解让系统知道我们希望使用哪个接口来创建WSDL,本例中就是
HelloWService接口。
@WebMethod 注释在了public方法上,这告诉了JAXWS,此方法为soap方法。
接口:
package com.server.ws; import java.util.Date; import javax.jws.WebService; import com.server.domain.PersonModel; /** * WebService接口 */ @WebService(name = "HelloWS", targetNamespace = "http://www.client.com/ws/hello") public interface HelloWService { /** * 返回字符串 * * @return */ String index(); /** * 两个整数相加 * * @param x * @param y * @return 相加后的值 */ Integer add(Integer x, Integer y); /** * 返回当前时间 * * @return */ Date now(); /** * 获取复杂类型 * * @param name * 用户姓名 * @param age * 用户年龄 * @return 返回用户类 */ PersonModel getPerson(String name, Integer age); }
实现类(注解@WebService及其endpointInterface属性是必要的):
package com.server.ws.impl; import java.util.Date; import javax.jws.HandlerChain; import javax.jws.WebService; import com.server.domain.PersonModel; import com.server.ws.HelloWService; @WebService( endpointInterface = "com.server.ws.HelloWService", portName = "HelloWSPort", serviceName = "HelloWSService", targetNamespace = "http://www.client.com/ws/hello" ) @HandlerChain(file="handler-chain.xml") public class HelloWServiceImpl implements HelloWService { public String index() { return "hello"; } public Integer add(Integer x, Integer y) { return x + y; } public Date now() { return new Date(); } public PersonModel getPerson(String name, Integer age) { PersonModel person = new PersonModel(); person.setAge(age); person.setName(name); return person; } }
实体对象:
package com.server.domain; import java.io.Serializable; public class PersonModel implements Serializable { private static final long serialVersionUID = -7211227324542440039L; private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
handler:
package com.server.ws.handler; import java.io.IOException; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPMessage; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; /** * 记录SOAP请求及响应 * */ public class LoggerSOAPHandler implements SOAPHandler<SOAPMessageContext> { @Override public void close(MessageContext context) { } @Override public boolean handleFault(SOAPMessageContext context) { return true; } @Override public boolean handleMessage(SOAPMessageContext context) { // 判断消息是输入还是输出 Boolean output = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); System.out.println(output ? "响应SOAP:" : "请求SOAP:"); SOAPMessage message = context.getMessage(); try { message.writeTo(System.out); } catch (SOAPException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } System.out.println(""); System.out.println(""); return true; } @Override public Set<QName> getHeaders() { return null; } }
package com.server.ws.handler; import java.util.Iterator; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPFault; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; /** * 服务端请求校验Handler * */ public class ValidateAuthHandler implements SOAPHandler<SOAPMessageContext> { @Override public void close(MessageContext context) { } @Override public boolean handleFault(SOAPMessageContext context) { return true; } @Override public boolean handleMessage(SOAPMessageContext context) { // 判断消息是请求还是响应 Boolean output = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); boolean result = false; SOAPMessage message = context.getMessage(); //如果是请求,则执行校验 if(!output){ result = validate(message); if(!result){ validateFail(message); } } System.out.println(output ? "服务端响应:" : "服务端接收:"); try { message.writeTo(System.out); } catch (Exception e) { e.printStackTrace(); } System.out.println("\r\n"); return result; } /** * 授权校验失败,在SOAPBody中添加SOAPFault * @param message */ private void validateFail(SOAPMessage message) { try { SOAPEnvelope envelop = message.getSOAPPart().getEnvelope(); envelop.getHeader().detachNode(); envelop.addHeader(); envelop.getBody().detachNode(); SOAPBody body = envelop.addBody(); SOAPFault fault = body.getFault(); if (fault == null) { fault = body.addFault(); } fault.setFaultString("授权校验失败!"); message.saveChanges(); } catch (SOAPException e) { e.printStackTrace(); } } /** * 授权校验 * @param message * @return 校验成功返回true,校验失败返回false */ private boolean validate(SOAPMessage message){ boolean result = false; try { SOAPEnvelope envelop = message.getSOAPPart().getEnvelope(); SOAPHeader header = envelop.getHeader(); if(header != null){ Iterator iterator = header.getChildElements(new QName("http://www.client.com/auth", "auth")); SOAPElement auth = null; if(iterator.hasNext()){ //获取auth auth = (SOAPElement)iterator.next(); //获取name Iterator it = auth.getChildElements(new QName("http://www.client.com/auth", "name")); SOAPElement name = null; if(it.hasNext()){ name = (SOAPElement)it.next(); } //获取password it = auth.getChildElements(new QName("http://www.client.com/auth", "password")); SOAPElement password = null; if(it.hasNext()){ password = (SOAPElement)it.next(); } //判断name和password是否符合要求 if(name != null && password != null && "admin".equals(name.getValue()) && "admin".equals(password.getValue())){ result = true; } } } } catch (SOAPException e) { e.printStackTrace(); } return result; } @Override public Set<QName> getHeaders() { return null; } }
2.2、发布
发布一般有两种方式:
方式一:Endpoint.publish
A、通过运行WebServicePublish类,就可以将编写好的WebService发布
package com.server.publish; import javax.xml.ws.Endpoint; import com.server.ws.impl.HelloWServiceImpl; /** * 发布Web Service * */ public class WebServicePublish { public static void main(String[] args) { // 定义WebService的发布地址,这个地址就是提供给外界访问Webervice的URL地址,URL地址格式为:http://ip:端口号/xxxx // String address = "http://192.168.1.100:8989/";这个WebService发布地址的写法是合法的 // String address = // "http://192.168.1.100:8989/Webservice";这个WebService发布地址的是合法的 String address = "http://localhost:8989/WS_Server/Webservice"; // 使用Endpoint类提供的publish方法发布WebService,发布时要保证使用的端口号没有被其他应用程序占用 Endpoint.publish(address, new HelloWServiceImpl()); System.out.println("发布webservice成功!"); } }
访问上面配置的地址http://localhost:8989/WS_Server/Webservice结果如下:
我的java工程结构是:
B、如果是Web项目,那么我们可以使用监听器或者Servlet来发布WebService,如下:
B.1、使用ServletContextListener监听器发布WebService
package com.server.publish; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.xml.ws.Endpoint; import com.server.ws.impl.HelloWServiceImpl; public class WebServicePublishListener implements ServletContextListener { @Override public void contextDestroyed(ServletContextEvent arg0) { // TODO Auto-generated method stub } @Override public void contextInitialized(ServletContextEvent arg0) { // 定义WebService的发布地址,这个地址就是提供给外界访问Webervice的URL地址,URL地址格式为:http://ip:端口号/xxxx // String address = "http://192.168.1.100:8989/";这个WebService发布地址的写法是合法的 // String address = // "http://192.168.1.100:8989/Webservice";这个WebService发布地址的是合法的 String address = "http://localhost:8081/WS_Server/Webservice"; // 使用Endpoint类提供的publish方法发布WebService,发布时要保证使用的端口号没有被其他应用程序占用 Endpoint.publish(address, new HelloWServiceImpl()); System.out.println("发布webservice成功!"); } }
xml配置如下:
<listener> <listener-class>com.server.publish.WebServicePublishListener</listener-class> </listener>
打包成war,部署到tomcat,并启动tomcat,访问及结果如下:
B.2、使用Servlet监听器发布WebService
package com.server.publish; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.xml.ws.Endpoint; import com.server.ws.impl.HelloWServiceImpl; /** * 用于发布WebService的Servlet,使用Servlet3.0提供的@WebServlet注解将继承HttpServlet类的普通Java类标注为一个Servlet, * 将value属性设置为空字符串,这样WebServicePublishServlet就不提供对外访问的路径 * loadOnStartup属性设置WebServicePublishServlet的初始化时机 * */ //@WebServlet(value="",loadOnStartup=0) public class WebServicePublishServlet extends HttpServlet { public void init() throws ServletException { //WebService的发布地址 String address = "http://localhost:8888/WebService"; //发布WebService,HelloWServiceImpl类是WebServie接口的具体实现类 Endpoint.publish(address , new HelloWServiceImpl()); System.out.println("使用WebServicePublishServlet发布webservice成功!"); } }
xml配置:
<servlet> <servlet-name>jaxws</servlet-name> <servlet-class>com.server.publish.WebServicePublishServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>jaxws</servlet-name> <url-pattern>/start</url-pattern> </servlet-mapping>
打包成war,部署到tomcat,并启动tomcat,访问及结果如下:
C、通过WebService配置文件sun-jaxws.xml
C.1、在WEB-INF中创建WebService配置文件sun-jaxws.xml,配置文件中一个WebService对应一个Endpoint。,如下:
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="hello" implementation="com.server.ws.impl.HelloWServiceImpl" url-pattern="/services/hello" /> </endpoints>
C.2、在web.xml中添加WSServlet,如果Web项目使用Servlet 3.0则不需要以下配置,如下:
web.xml文件:
<!-- JAXWS --> <listener> <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class> </listener> <servlet> <servlet-name>jaxws</servlet-name> <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>jaxws</servlet-name> <url-pattern>/services</url-pattern> </servlet-mapping> <!-- End JAXWS -->
打包成war,部署到tomcat,并启动tomcat,访问及结果如下:
2.2、生成客户端
最后是客户端使用,由于WebService是平台和语言无关的基于xml的,所以我们完全可以使用不同语言来编写或生成客户端。
一般有三种方式来使用(对于Java语言而言):
一、使用jdk自带工具wsimport生成客户端:
jdk自带的wsimport工具生成,上图我是把客户端文件生成到了桌面src文件中(-d),并保留了源文件(-keep),指定了包名(-p)。
然后我们就可以使用生成的文件来调用服务器暴露的方法了:
值得一提的是你生成使用的jdk和你客户端的jre需要配套!
从上面的目录结构我们可以发现:服务端的每个webmethod都被单独解析成为了一个类(如果使用了实体,实体也会被解析到客户端,并且是源码,所以建议使用实体时慎重)。
而我们的service则被生成了一个代理类来调用服务,接下来我们看看使用情况,在客户端调用服务:
package com.client; import com.client.wsdl.hello.HelloWS; import com.client.wsdl.hello.HelloWSService; import com.client.wsdl.hello.PersonEntity; /** * 调用WebService的客户端 * */ public class WSClient { public static void main(String[] args) { // 创建一个用于产生WebServiceImpl实例的工厂,WebServiceImplService类是wsimport工具生成的 HelloWSService factory = new HelloWSService(); // 通过工厂生成一个WebServiceImpl实例,WebServiceImpl是wsimport工具生成的 HelloWS wsImpl = factory.getHelloWSPort(); // 调用WebService的add方法 Integer resResult = wsImpl.add(10, 22); System.out.println("调用WebService的add方法返回的结果是:" + resResult); System.out.println("---------------------------------------------------"); // 调用WebService的save方法 PersonEntity pm = wsImpl.getPerson("孤傲苍狼", 123); System.out.println("调用WebService的getPerson方法返回的结果是:" + pm.getName() + ",age=" + pm.getAge()); } }
handler
package com.client.handler; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; public class AddAuthHandler implements SOAPHandler<SOAPMessageContext> { @Override public boolean handleMessage(SOAPMessageContext context) { // 判断消息是请求还是响应 Boolean output = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); SOAPMessage message = context.getMessage(); if (output) { try { SOAPHeader header = message.getSOAPHeader(); if (header == null) { header = message.getSOAPPart().getEnvelope().addHeader(); } SOAPElement auth = header.addChildElement(new QName("http://www.client.com/auth", "auth")); SOAPElement name = auth.addChildElement("name"); name.addTextNode("admin"); SOAPElement password = auth.addChildElement("password"); password.addTextNode("admin"); message.saveChanges(); } catch (SOAPException e) { e.printStackTrace(); } } System.out.println(output ? "校验" : "不校验"); try { message.writeTo(System.out); } catch (Exception e) { e.printStackTrace(); } System.out.println("\r\n"); return true; } @Override public boolean handleFault(SOAPMessageContext context) { return true; } @Override public void close(MessageContext context) { } @Override public Set<QName> getHeaders() { return null; } }
看看服务器的输出,我们是否调用成功:
成功了!
2 client端的servlet方式
package com.client.servlet.hello; import java.io.IOException; import java.net.URL; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.client.wsdl.hello.HelloWS; import com.client.wsdl.hello.HelloWSService; @WebServlet("/hello/add/servlet") public class AddServlet extends HttpServlet { private static final long serialVersionUID = 1L; public AddServlet() { super(); } protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { URL wsdlUrl = new URL("http://localhost:8080/jaxwsserver-0.0.1-SNAPSHOT/services/hello?wsdl"); HelloWSService helloWSS = new HelloWSService(wsdlUrl); HelloWS helloWS = helloWSS.getHelloWSPort(); Integer x = 3; Integer y = 5; Integer add = helloWS.add(x, y); response.setCharacterEncoding("utf-8"); response.setContentType("text/plain;charset=utf-8"); response.getWriter().write(x.toString() + y.toString() + "=" + add.toString()); } }
Ayncnow方法
package com.client.servlet.hello; import java.io.PrintWriter; import java.net.URL; import javax.servlet.AsyncContext; import javax.servlet.http.HttpServletResponse; import com.client.wsdl.hello.HelloWS; import com.client.wsdl.hello.HelloWSService; public class AsyncNowServletProcessor extends Thread { private AsyncContext ac; public AsyncNowServletProcessor(AsyncContext ac){ this.ac = ac; } public void run() { HttpServletResponse response = (HttpServletResponse)ac.getResponse(); response.setCharacterEncoding("utf-8"); response.setContentType("text/plain;charset=utf-8"); try { URL wsdlUrl = new URL("http://localhost:8080/jaxwsserver-0.0.1-SNAPSHOT/services/hello?wsdl"); HelloWSService helloWSS = new HelloWSService(wsdlUrl); HelloWS helloWS = helloWSS.getHelloWSPort(); helloWS.now(); PrintWriter out = response.getWriter(); String now = helloWS.now().toGregorianCalendar().toString(); out.write(now); out.flush(); } catch (Exception e) { e.printStackTrace(); }finally{ ac.complete(); } } }
index方法
package com.client.servlet.hello; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.ws.handler.Handler; import javax.xml.ws.handler.HandlerResolver; import javax.xml.ws.handler.PortInfo; import com.client.wsdl.hello.HelloWS; import com.client.wsdl.hello.HelloWSService; import com.client.handler.AddAuthHandler; /** * Servlet implementation class IndexServlet */ @WebServlet("/hello/index/servlet") public class IndexServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public IndexServlet() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#service(HttpServletRequest request, HttpServletResponse response) */ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { URL wsdlUrl = new URL("http://localhost:8080/jaxwsserver-0.0.1-SNAPSHOT/services/hello?wsdl"); HelloWSService helloWSS = new HelloWSService(wsdlUrl); //通过HandlerResolver添加Handler helloWSS.setHandlerResolver(new HandlerResolver(){ @Override @SuppressWarnings("rawtypes") public List<Handler> getHandlerChain(PortInfo portInfo) { List<Handler> handlerChain = new ArrayList<Handler>(); handlerChain.add(new AddAuthHandler()); return handlerChain; } }); HelloWS helloWS = helloWSS.getHelloWSPort(); response.setCharacterEncoding("utf-8"); response.setContentType("text/plain;charset=utf-8"); response.getWriter().write(helloWS.index()); } }
now方法
package com.client.servlet.hello; import java.io.IOException; import java.io.PrintWriter; import java.net.URL; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.client.wsdl.hello.HelloWS; import com.client.wsdl.hello.HelloWSService; /** * Servlet implementation class NowServlet */ @WebServlet(value = "/hello/now/servlet", asyncSupported = true) public class NowServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public NowServlet() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#service(HttpServletRequest request, HttpServletResponse response) */ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { URL wsdlUrl = new URL("http://localhost:8080/jaxwsserver-0.0.1-SNAPSHOT/services/hello?wsdl"); HelloWSService helloWSS = new HelloWSService(wsdlUrl); HelloWS helloWS = helloWSS.getHelloWSPort(); String now = helloWS.now().toGregorianCalendar().toString(); response.setCharacterEncoding("utf-8"); response.setContentType("text/plain;charset=utf-8"); //new AsyncNowServletProcessor(null).start(); PrintWriter out = response.getWriter(); out.write(now); out.flush(); /* AsyncContext ac = request.startAsync(request, response); ac.addListener(new AsyncListener(){ public void onComplete(AsyncEvent arg0) throws IOException { } public void onError(AsyncEvent arg0) throws IOException { } public void onStartAsync(AsyncEvent arg0) throws IOException { } public void onTimeout(AsyncEvent arg0) throws IOException { } }, request, response); new AsyncNowServletProcessor(ac).start(); */ } }
person
package com.client.servlet.hello; import java.io.IOException; import java.net.URL; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.client.wsdl.hello.HelloWS; import com.client.wsdl.hello.HelloWSService; import com.client.wsdl.hello.PersonEntity; /** * Servlet implementation class PersonServlet */ @WebServlet("/hello/person/servlet") public class PersonServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public PersonServlet() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#service(HttpServletRequest request, HttpServletResponse response) */ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { URL wsdlUrl = new URL("http://localhost:8080/jaxwsserver-0.0.1-SNAPSHOT/services/hello?wsdl"); HelloWSService helloWSS = new HelloWSService(wsdlUrl); HelloWS helloWS = helloWSS.getHelloWSPort(); PersonEntity person = helloWS.getPerson("年龄", 18); response.setCharacterEncoding("utf-8"); response.setContentType("text/plain;charset=utf-8"); response.getWriter().write("名字;" + person.getName() + ",年龄;" + person.getAge()); } }