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文檔