Webservice详解
WebService是什么?
1. 基于Web的服务:服务器端整出一些资源让客户端应用访问(获取数据)
2. 一个跨语言、跨平台的规范(抽象)
3. 多个跨平台、跨语言的应用间通信整合的方案(实际)
以各个网站显示天气预报功能为例:
气象中心的管理系统将收集的天气信息并将数据暴露出来(通过WebService Server), 而各大站点的应用就去调用它们得到天气信息并以不同的样式去展示(WebService Client)。网站提供了天气预报的服务,但其实它们什么也没有做,只是简单了调用了一下气象中心服务器上的一个服务接口而已。
4. WebService的优点:能够解决跨平台,跨语言,以及远程调用之间的问题。
5. WebService的应用场合
a. 同一家公司的新旧应用之间
b. 不同公司的应用之间,例如电商和物流之间的应用相互调用
c. 一些提供数据的内容聚合应用:天气预报、股票行情
WebService预备知识
几个重要术语
1.WSDL/web service definition language
webservice定义语言, 对应.wsdl文档, 一个webservice会对应一个唯一的wsdl文档, 定义了客户端与服务端发送请求和响应的数据格式和过程。
2.SOAP/simple object access protocal
一种简单的、基于HTTP和XML的协议, 用于在WEB上交换结构化的数据,在webservice中分为请求消息和响应消息。
3.SEI/WebService EndPoint Interface
webService服务器端用来处理请求的接口
4.CXF/Celtix + XFire
一个apache的用于开发webservice服务器端和客户端的框架
WebService开发
开发方式:
1. 使用JDK开发(JDK6及以上版本)
2. 使用apache CXF开发(工作中)
组成:
1. 服务端开发
2. 客户端开发
一. 使用JDK开发webservice
服务端:
/** * SEI * @author byron */ @WebService public interface HelloWs { @WebMethod public String sayHello(String name); } /** * SEI的实现 * @author byron */ @WebService public class HelloWsImpl implements HelloWs { @WebMethod public String sayHello(String name) { System.out.println("sayHello " + name); return "hello " + name; } } /** * 发布webservice * @author byron */ public class WsPublish { public static void main(String[] args) { Endpoint.publish("http://192.168.1.106:8989/ws/hello", new HelloWsImpl()); System.out.println("发布Webservice 服务成功!"); } }
发布的webservice服务的wsdl的URL为:http://192.168.1.106:8989/ws/hello?wsdl
客户端:
1. 进入生成客户端代码的目录,执行jdk自带的生成客户端代码的命令:wsimport -keep http://192.168.1.106:8989/ws/hello?wsdl
2. 调用webservice服务
/** * 调用webservice * @author byron */ public class HelloClient { public static void main(String[] args) { HelloWsImplService hws = new HelloWsImplService(); HelloWs ws = hws.getHelloWsImplPort(); String result = ws.sayHello("Jack"); System.out.println("Result:" + result); } }
二. WSDL文件分析
。。。。。
三. 使用CXF开发WebService
服务端:
添加apache CXF相关jar包,代码参照JDK开发无需修改
使用maven构建项目时,在pom.xml添加如下依赖即可:
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>2.7.18</version> </dependency>
客户端:
下载apache cxf软件,解压后,设置环境变量CXF_HOME,将$CXF_HOME/bin加入PATH,然后可以使用wsdl2java生成客户端WebService代码,调用方式与JDK方式相同。
使用maven构建项目时,在pom.xml添加如下依赖即可:
<dependencies> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>apache-cxf</artifactId> <version>${cxf.version}</version> <type>pom</type> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>${cxf.version}</version> </dependency> </dependencies>
1. CXF支持的数据类型
基本类型:int/float/boolean等
引用类型:String, 数组/List/Set/Map, 自定义类型(Student)
示例代码:
// 服务端代码 @WebService public interface DataTypeWS { @WebMethod public boolean addStudent(Student student); @WebMethod public Student getStudentById(int id); @WebMethod public List<Student> getStudentsByPrice(float price); @WebMethod public Map<Integer, Student> getAllStudentsMap(); } @WebService public class DataTypeWSImpl implements DataTypeWS { @Override public boolean addStudent(Student student) { System.out.println("Server addStudent..."); return true; } @Override public Student getStudentById(int id) { System.out.println("Server getStudentById..."); return new Student(id, "Tom", 8500); } @Override public List<Student> getStudentsByPrice(float price) { System.out.println("Server getStudentsByPrice..."); List<Student> list = new ArrayList<>(); list.add(new Student(1, "Jim", price + 1000)); list.add(new Student(2, "Tim", price + 2000)); list.add(new Student(3, "Lim", price + 3000)); return list; } @Override public Map<Integer, Student> getAllStudentsMap() { System.out.println("Server getStudentsMap..."); Map<Integer, Student> map = new HashMap<>(); map.put(1, new Student(1, "Jim", 1000)); map.put(2, new Student(2, "Tim", 2000)); map.put(3, new Student(3, "Lim", 3000)); return map; } } /** * 发布服务 */ public class DataTypeServer { public static void main(String[] args) { Endpoint.publish("http://192.168.1.110:8990/ws/type", new DataTypeWSImpl()); System.out.println("发布 DataType Webservice 服务成功!"); } }
使用wsdl2java命令生成客户端webservice代码,客户端调用代码如下:
public class DataTypeClientTest { @Test public void testInteger() { DataTypeWSImplService factory = new DataTypeWSImplService(); DataTypeWS dataTypeWS = factory.getDataTypeWSImplPort(); Student student = dataTypeWS.getStudentById(1); System.out.println(student); } @Test public void testObject() { DataTypeWSImplService factory = new DataTypeWSImplService(); DataTypeWS dataTypeWS = factory.getDataTypeWSImplPort(); dataTypeWS.addStudent(new Student(10, "Tony", 12000)); } @Test public void testList() { DataTypeWSImplService factory = new DataTypeWSImplService(); DataTypeWS dataTypeWS = factory.getDataTypeWSImplPort(); List<Student> students = dataTypeWS.getStudentsByPrice(5000); for (Student stu : students) { System.out.println(stu); } } @Test public void testMap() { DataTypeWSImplService factory = new DataTypeWSImplService(); DataTypeWS dataTypeWS = factory.getDataTypeWSImplPort(); Return ret = dataTypeWS.getAllStudentsMap(); List<Entry> entrys = ret.getEntry(); for (Entry e : entrys) { System.out.println(e.getKey() + ">>" + e.getValue()); } } }
说明:调用成功说明CXF支持上述数据类型;将项目向CXF相关的jar包移除,让webservice以JDK api方式运行,验证得知JDK不支持Map类型.
2. CXF拦截器
作用:在文本service请求过程中,动态操作请求和响应数据。
分类:按位置分(服务端拦截器 & 客户端拦截器),按消息方向分(入拦截器 & 出拦截器),按定义方式分(系统拦截器&自定义拦截器)
① 服务端拦截器的使用方法,代码如下:
public class HelloInterceptor { public static void main(String[] args) { Endpoint publish = Endpoint.publish("http://192.168.1.110:8989/ws/hello", new HelloWsImpl()); EndpointImpl impl = (EndpointImpl) publish; // 服务端的日志入拦截器 List<Interceptor<? extends Message>> inInterceptors = impl.getInInterceptors(); inInterceptors.add(new LoggingInInterceptor()); // 服务端的日志出拦截器 List<Interceptor<? extends Message>> outInterceptors = impl.getOutInterceptors(); outInterceptors.add(new LoggingInInterceptor()); System.out.println("发布Webservice 服务成功!"); } }
重新发布webservice,并生成客户端基础代码,再调用webservice,可以发现在服务端会打印请求和响应信息的日志。
② 同理,客户端也可以添加拦截器
public class InterceptClient { public static void main(String[] args) { InterceptorWsImplService fatory = new InterceptorWsImplService(); InterceptorWs interceptorWs = fatory.getInterceptorWsImplPort(); // 获取发送请求的客户端 Client client = ClientProxy.getClient(interceptorWs); // 客户单日志出拦截器 List<Interceptor<? extends Message>> outInterceptors = client.getOutInterceptors(); outInterceptors.add(new LoggingInInterceptor()); // 客户端日志入拦截器 List<Interceptor<? extends Message>> inInterceptors = client.getInInterceptors(); inInterceptors.add(new LoggingInInterceptor()); String result = interceptorWs.intercept("Tom"); System.out.println("Client " + result); } }
3. CXF自定义拦截器
下面使用示例说明cxf中自定义拦截器的用户,在代码中定义了一个校验用户名密码的拦截器,代码如下:
客户端使用出拦截器附加上用户名密码信息:
/** * 客户端发送webservice前的拦截器,将用户名密码加入请求消息 * 自定义拦截器继承AbstractPhaseInterceptor即可 */ public class AddUserInterceptor extends AbstractPhaseInterceptor<SoapMessage> { private String username; private String password; public AddUserInterceptor(String username, String password) { // 在构造器中指定拦截器拦截的时机 super(Phase.PRE_PROTOCOL); this.username = username; this.password = password; } @Override public void handleMessage(SoapMessage message) throws Fault { List<Header> headers = message.getHeaders(); Document document = DOMUtils.createDocument(); Element element = document.createElement("user"); Element nameEle = document.createElement("username"); nameEle.setTextContent(username); element.appendChild(nameEle); Element pwdEle = document.createElement("password"); pwdEle.setTextContent(password); element.appendChild(pwdEle); headers.add(new Header(new QName("user"), element)); System.out.println("Client User Interceptor..."); } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } /** * 客户单拦截器测试代码 */ public class CustomInterceptClient { public static void main(String[] args) { InterceptorWsImplService fatory = new InterceptorWsImplService(); InterceptorWs interceptorWs = fatory.getInterceptorWsImplPort(); // 获取发送请求的客户端 Client client = ClientProxy.getClient(interceptorWs); // 客户单日志出拦截器 List<Interceptor<? extends Message>> outInterceptors = client.getOutInterceptors(); outInterceptors.add(new AddUserInterceptor("Tom", "12345")); String result = interceptorWs.intercept("Tom"); System.out.println("Client " + result); } }
服务端使用入拦截器校验用户名密码:
/** * 服务端接收webservice请求前的拦截器,校验用户名密码 */ public class CheckUserInterceptor extends AbstractPhaseInterceptor<SoapMessage> { public CheckUserInterceptor() { // 在构造器中指定拦截器拦截的时机 super(Phase.PRE_PROTOCOL); } @Override public void handleMessage(SoapMessage message) throws Fault { Header header = message.getHeader(new QName("user")); if (header != null) { Element user = (Element) header.getObject(); String username = user.getElementsByTagName("username").item(0).getTextContent(); String password = user.getElementsByTagName("password").item(0).getTextContent(); if ("Tom".equals(username) && "12345".equals(password)) { System.out.println("拦截器校验通过..."); return; } } System.out.println("拦截器校验失败..."); throw new Fault(new RuntimeException("用户名或密码不正确!")); } } /** * 服务端带拦截器发布 */ public class CustomInterceptorServer { public static void main(String[] args) { Endpoint publish = Endpoint.publish("http://192.168.1.110:8991/ws/intercept", new InterceptorWsImpl()); EndpointImpl impl = (EndpointImpl) publish; // 服务端的自定义入拦截器 List<Interceptor<? extends Message>> inInterceptors = impl.getInInterceptors(); inInterceptors.add(new CheckUserInterceptor()); System.out.println("发布Webservice 服务成功!"); } }
4. 基于Spring的CXF webservice
1>webservice服务端代码如下:
public class Order { private int id; private String name; private double price; public Order() { super(); } public Order(int id, String name, double price) { super(); this.id = id; this.name = name; this.price = price; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Order [id="); builder.append(id); builder.append(", name="); builder.append(name); builder.append(", price="); builder.append(price); builder.append("]"); return builder.toString(); } } @WebService public class OrderWSImpl implements OrderWS { public OrderWSImpl() { System.out.println("OrderWSImpl constructor..."); } @Override public Order getOrderById(int id) { System.out.println("Server getOrderById..."); return new Order(1001, "TD002", 2000); } }
spring配置(bean.xml)如下:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"> <import resource="classpath:META-INF/cxf/cxf.xml" /> <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" /> <import resource="classpath:META-INF/cxf/cxf-servlet.xml" /> <jaxws:endpoint id="orderWS" implementor="com.stu.ws.spring.OrderWSImpl" address="/orderws" /> </beans>
web.xml配置如下:
<web-app id="WebApp_ID" version="3.0" 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_3_0.xsd"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:bean.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>CXFServlet</servlet-name> <servlet-class> org.apache.cxf.transport.servlet.CXFServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
把上述项目直接部署到tomcat即可。可以通过 http://localhost:8080/ws.server/orderws?wsdl访问到wsdl文档
2>客户端代码开发:
进入客户端项目所在目录,使用命令 "wsdl2java http://localhost:8080/ws.server/orderws?wsdl" 生成客户端代码。
spring配置文件(client-bean.xml)
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"> <jaxws:client id="orderClient" serviceClass="com.stu.ws.spring.OrderWS" address="http://localhost:8080/ws.server/orderws" /> </beans>
编写客户端调用代码:
public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("client-bean.xml"); OrderWS orderWs = (OrderWS) context.getBean("orderClient"); Order order = orderWs.getOrderById(1002); System.out.println(order); }
5. 基于spring的CXF拦截器
复用3中得拦截器代码和4中得配置,进行修改即可
服务端配置入拦截器:bean.xml
<jaxws:endpoint id="orderWS" implementor="com.stu.ws.spring.OrderWSImpl" address="/orderws" > <jaxws:inInterceptors> <bean class="com.stu.ws.interceptor.custom.CheckUserInterceptor"/> </jaxws:inInterceptors> </jaxws:endpoint>
客户端出拦截器配置:client-bean.xml
<jaxws:client id="orderClient" serviceClass="com.stu.ws.spring.OrderWS" address="http://localhost:8080/ws.server/orderws" > <jaxws:outInterceptors> <bean class="com.stu.ws.user.AddUserInterceptor"> <constructor-arg name="username" value="Tom" /> <constructor-arg name="password" value="12345" /> </bean> </jaxws:outInterceptors> </jaxws:client>
6. 使用Ajax请求webservice
在页面自行组装符合格式要求的请求数据发送post请求即可,代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Ajax 请求 Webservice</title> <script type="text/javascript" src="js/jquery-1.10.1.min.js"></script> <script type="text/javascript"> $(function(){ var sendData = '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' + '<soap:Body><ns2:intercept xmlns:ns2="http://interceptor.ws.stu.com/">' + '<arg0>' + $('#username').val() + '</arg0>' + '</ns2:intercept></soap:Body></soap:Envelope>'; $("#requestWs2").click(function(){ $.post( "http://localhost:8080/ws.server/intercept", sendData, function(msg) { var result = $(msg).find("return").text(); alert(result); }, "xml" ); }); }) // JS发送Ajax请求 function requestWs1() { var request = getRequest(); request.onreadystatechange = function(){ if (request.readyState == 4 && request.status == 200) { var result = request.responseXML; var value = result.getElementsByTagName("return")[0].firstChild.data; alert(value); } } request.open("POST", "http://localhost:8080/ws.server/intercept"); request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); var sendData = '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' + '<soap:Body><ns2:intercept xmlns:ns2="http://interceptor.ws.stu.com/">' + '<arg0>' + document.getElementById('username').value + '</arg0>' + '</ns2:intercept></soap:Body></soap:Envelope>'; alert(sendData); request.send(sendData); } function getRequest() { var xmlHttp = null; try { xmlHttp = new XMLHttpRequest(); } catch(e) { try { xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); } } return xmlHttp; } </script> </head> <body> 用户名:<input id="username" name="username" value="" /> <button onclick="requestWs1()">Ajax请求ws(Js)</button> <button id="requestWs2">Ajax请求ws(jQ)</button> </body> </html>
本次测试中,请求页面和webservice服务都在同一台机器,使用相同的IP访问,否则在页面请求会发生跨域问题。
跨域问题解决方法:在页面请求后台的一个Servlet,在Java代码去请求webservice,即可。
页面代码修改如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Ajax 请求 Webservice</title> <script type="text/javascript" src="js/jquery-1.10.1.min.js"></script> <script type="text/javascript"> $(function(){ $("#requestWs2").click(function(){ $.post( "wsHelperServlet", {username:$('#username').val()}, function(msg) { var result = $(msg).find("return").text(); alert(result); }, "text" ); }); }) </script> </head> <body> 用户名:<input id="username" name="username" value="" /> <button id="requestWs2">Ajax请求ws(jQ)</button> </body> </html>
Servlet代码如下:
@WebServlet("/wsHelperServlet") public class WsHelperServlet extends HttpServlet { private static final long serialVersionUID = 1L; public WsHelperServlet() { super(); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String name = request.getParameter("username"); String data = "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" + "<soap:Body><ns2:intercept xmlns:ns2=\"http://interceptor.ws.stu.com/\">" + "<arg0>" + name + "</arg0>" + "</ns2:intercept></soap:Body></soap:Envelope>"; URL url = new URL("http://192.168.1.110:8080/ws.server/intercept"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setDoInput(true); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "text/xml;charset=UTF-8"); conn.getOutputStream().write(data.getBytes("UTF-8")); response.setContentType("text/xml;charset=utf-8"); if (conn.getResponseCode() == 200) { InputStream is = conn.getInputStream(); System.out.println(is.available()); ServletOutputStream os = response.getOutputStream(); int len = 0; byte[] buffer = new byte[1024]; while ((len=is.read(buffer)) > 0) { os.write(buffer, 0, len); } os.flush(); } } }
8. 通过注解修改wsdl文档