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()); } }