因為最近開發的項目需求中涉及到了webservice,正好對這塊知識以前學過但是又忘記了,於是想着從新學習下,整理一個筆記,便於后面的復習。於是有了本文,下面開始介紹webservice。
一、簡介
大家或多或少都聽過 WebService(Web服務),有一段時間甚至很多計算機期刊、書籍和網站都大肆的提及和宣傳WebService技術,其中不乏很多吹噓和做廣告的成 分。但是不得不承認的是WebService真的是一門新興和有前途的技術,那么WebService到底是什么?何時應該用?下面將會詳細介紹,這一節我們先有一個感性認識。具體舉個例子,比如在Windows Server服務器上有個C#.Net開發的應用A,在Linux上有個Java語言開發的應用B,B應用要調用A應用,或者是互相調用。用於查看對方的業務數據。再舉個例子,天氣預報接口。無數的應用需要獲取天氣預報信息;這些應用可能是各種平台,各種技術實現;而氣象局的項目,估計也就一兩種,要對外提供天氣預報信息,這個時候,如何解決呢?這些應用的時候的都可以通過WebService來很好的實現其應用。通過Web Service,客戶端和服務器才能夠自由的用HTTP進行通信,不論兩個程序的平台和編程語言是什么。當然有人會說使用Socket通信業可以達到效果,但是兩者之間還是有區別的。比如:
客戶端:
package com.pony1223; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; public class WeatherClient { public static void main(String[] args) throws UnknownHostException, IOException { //1.創建Socket對象,和服務端建立連接 Socket socket = new Socket("127.0.0.1",12345); //2.發送城市名稱 DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeUTF("北京"); System.out.println("請求查詢天氣: 北京"); //3.接受返回結果使用輸入流 DataInputStream dis = new DataInputStream(socket.getInputStream()); String result = dis.readUTF(); System.out.println("北京的天氣: " + result); //4.關閉流 dis.close(); dos.close(); } }
服務端:
package com.pony1223; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class WeatherServer { public static void main(String[] args) throws IOException{ // 1.創建ServerSocket對象 ServerSocket serverSocket = new ServerSocket(12345); while(true){ // 2.等待客戶端連接,阻塞的方法 final Socket socket = serverSocket.accept(); Runnable runnable = new Runnable(){ @Override public void run(){ try{ // 3.使用輸入流接受客戶端發送的請求 DataInputStream dis = new DataInputStream(socket.getInputStream()); String cityName = dis.readUTF(); System.out.println("接收到客戶端發送的請求: " + cityName); Thread.sleep(1000); // 4.根據城市名查詢天氣 String result = "今天天氣很熱"; System.out.println("返回天氣信息: " + result); // 5.返回查詢結果,使用輸出流。 DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeUTF(result); // 6.關閉流 dis.close(); dos.close(); }catch(Exception e){ e.printStackTrace(); } } }; //啟動線程 new Thread(runnable).start(); } } }
輸出結果為:
請求查詢天氣: 北京
北京的天氣: 今天天氣很熱
接收到客戶端發送的請求: 北京
返回天氣信息: 今天天氣很熱
然后我們采用WebService來實現下:
第一步: 創建一個java工程,不需要導入jar包。
第二步: 創建接口。在Webservice中叫做SEI(wsdl中叫做PortType)。
第三步: 創建實現類。需要在實現類上加一個@Webservice注解。
第四步: 發布服務。EndPoint.publish發布服務。
WeatherInterface.java
package com.pony1223.wsservice; import javax.jws.WebService; @WebService public interface WeatherInterface { String getWeatherByCity(String city); }
實現類:
package com.pony1223.wsservice.impl; import javax.jws.WebService; import com.pony1223.wsservice.WeatherInterface; @WebService public class WeatherInterfaceImpl implements WeatherInterface { @Override public String getWeatherByCity(String city) { System.out.println("接收客戶端發送過來的城市名字:"+city); //調用天氣等服務 //..... //這里模擬所以直接返回結果 String result = "天氣比較冷"; System.out.println("返回天氣查詢結果:"+result); return result; } }
WeatherServer.java 發布服務;
package com.pony1223.test; import javax.xml.ws.Endpoint; import com.pony1223.wsservice.WeatherInterface; import com.pony1223.wsservice.impl.WeatherInterfaceImpl; public class WeatherServer { public static void main(String[] args) { WeatherInterface server = new WeatherInterfaceImpl(); String address = "http://192.168.31.159:1111/WeatherInterface"; Endpoint.publish(address, server); } }
看看服務是否發布成功,訪問wsdl:http://192.168.31.159:1111/WeatherInterface?wsdl
可以看到服務發布成功:
客戶端代碼編寫,可以采用工具生成:
刷新工程:
因此我們只需要編寫client的調用即可:
第1步:創建服務視圖對象。
第2步: 從服務視圖中獲得PortType對象。
第3步: 調用PortType的方法(可以實現遠程通信)
第4步: 接收方法的返回值(服務端響應的結果)。
package com.pony1223.wsservice.impl; public class Client { public static void main(String[] args) { WeatherInterfaceImplService server = new WeatherInterfaceImplService(); WeatherInterfaceImpl impl = server.getWeatherInterfaceImplPort(); System.out.println(impl.getWeatherByCity("北京")); } }
輸出結果:
天氣比較冷
接收客戶端發送過來的城市名字:北京
返回天氣查詢結果:天氣比較冷
上面編寫了兩種方式,那么區別是什么?
(1)Socket是基於TCP/IP的傳輸層協議。
Webservice是基於HTTP協議傳輸數據,http是基於tcp的應用層協議。
Webservice采用了基於http的soap協議傳輸數據。
(2)Socket接口通過流傳輸,不支持面向對象。
Webservice 接口支持面向對象,最終webservice將對象進行序列化后通過流傳輸。
Webservice采用soap協議進行通信,不需專門針對數據流的發送和接收進行處理,是一種跨平台的面向對象遠程調用技術。
(3)Socket適用於高性能大數據的傳輸,傳輸的數據需要手動處理,socket通信的接口協議需要自定義。
比如:自定義一種字符串拼接的格式,比如自定義的xml數據,自定義麻煩之處在接口調用方和接口服務端需要互相討論確定接口的協議內容,不方便。
缺點
程序員需要自己去解析輸入、輸出流,解析發送和接收的數據。數據傳輸的格式不固定,需要程序員開發socket接口時自定義接口協議。
優點
如果要傳輸大數據量,socket可以滿足,如果存在大並發使用socket也可以實現,程序用socket靈活性更大,比如可以socket的高並發框架mina開發。
Webservcie由於是遵循標准的soap協議,soap 協議的內容格式固定,soap協議傳遞的內容是xml數據,由於webservice是基於http的,所以簡單理解為soap=http+xml,適用於沒有性能要求情況下且數據傳輸量小,推薦在公開接口上使用webservice,因為soap協議的標准的。
優點
jaxws可以通過面向對象開發webservice,程序員不需要解析輸入、輸出流。
由於webservice傳輸數據使用標准的soap協議(基於http傳輸xml),soap協議已經被w3c管理了。
缺點
如果傳輸大數據量,webservice不適用。如果webservice開發大並發的應用,webservice依靠web容器提高並發數。
說明:大部分場景,WebService 已經足夠使用,所以本文的的重點是webservice.
二、WebService的本質
一句話:WebService是一種跨語言和跨平台的遠程調用技術。
所謂跨編程語言和跨操作平台,就是說服務端程序采用java編寫,客戶端程序則可以采用其他編程語言編寫,反之亦然!跨操作系統平台則是指服務端程序和客戶端程序可以在不同的操作系統上運行。
所謂遠程調用,就是一台計算機a上 的一個程序可以調用到另外一台計算機b上的一個對象的方法,譬如,銀聯提供給商場的pos刷卡系統,商場的POS機轉賬調用的轉賬方法的代碼其實是跑在銀 行服務器上。再比如,amazon,天氣預報系統,淘寶網,校內網,百度等把自己的系統服務以webservice服務的形式暴露出來,讓第三方網站和程 序可以調用這些服務功能,這樣擴展了自己系統的市場占有率,往大的概念上吹,就是所謂的SOA應用。
其實可以從多個角度來理解 WebService,從表面上看,WebService就是一個應用程序向外界暴露出一個能通過Web進行調用的API,也就是說能用編程的方法通過 Web來調用這個應用程序。我們把調用這個WebService的應用程序叫做客戶端,而把提供這個WebService的應用程序叫做服務端。從深層次 看,WebService是建立可互操作的分布式應用程序的新平台,是一個平台,是一套標准。它定義了應用程序如何在Web上實現互操作性,你可以用任何 你喜歡的語言,在任何你喜歡的平台上寫Web service ,只要我們可以通過Web service標准對這些服務進行查詢和訪問。
WebService平台需要一套協議來實現分布式應用程序的創建。任何平台都有它的數據表示方法和類型系統。要實現互操作性,WebService平台 必須提供一套標准的類型系統,用於溝通不同平台、編程語言和組件模型中的不同類型系統。Web service平台必須提供一種標准來描述 Web service,讓客戶可以得到足夠的信息來調用這個Web service。最后,我們還必須有一種方法來對這個Web service進行遠 程調用,這種方法實際是一種遠程過程調用協議(RPC)。為了達到互操作性,這種RPC協議還必須與平台和編程語言無關。
三、WebService的技術基礎
XML+XSD,SOAP和WSDL就是構成WebService平台的三大技術。
3.1、XML+XSD
WebService采用HTTP協議傳輸數據,采用XML格式封裝數據(即XML中說明調用遠程服務對象的哪個方法,傳遞的參數是什么,以及服務對象的 返回結果是什么)。XML是WebService平台中表示數據的格式。除了易於建立和易於分析外,XML主要的優點在於它既是平台無關的,又是廠商無關 的。無關性是比技術優越性更重要的:軟件廠商是不會選擇一個由競爭對手所發明的技術的。
XML解決了數據表示的問題,但它沒有定義一套標准的數據類型,更沒有說怎么去擴展這套數據類型。例如,整形數到底代表什么?16位,32位,64位?這 些細節對實現互操作性很重要。XML Schema(XSD)就是專門解決這個問題的一套標准。它定義了一套標准的數據類型,並給出了一種語言來擴展這套數據類型。WebService平台就 是用XSD來作為其數據類型系統的。當你用某種語言(如VB.NET或C#)來構造一個Web service時,為了符合WebService標准,所 有你使用的數據類型都必須被轉換為XSD類型。你用的工具可能已經自動幫你完成了這個轉換,但你很可能會根據你的需要修改一下轉換過程。
3.2、SOAP
WebService通過HTTP協議發送請求和接收結果時,發送的請求內容和結果內容都采用XML格式封裝,並增加了一些特定的HTTP消息頭,以說明 HTTP消息的內容格式,這些特定的HTTP消息頭和XML內容格式就是SOAP協議。SOAP提供了標准的RPC方法來調用Web Service。
SOAP協議 = HTTP協議 + XML數據格式
SOAP協議定義了SOAP消息的格式,SOAP協議是基於HTTP協議的,SOAP也是基於XML和XSD的,XML是SOAP的數據編碼方式。打個比 喻:HTTP就是普通公路,XML就是中間的綠色隔離帶和兩邊的防護欄,SOAP就是普通公路經過加隔離帶和防護欄改造過的高速公路。
3.3、WSDL
好比我們去商店買東西,首先要知道商店里有什么東西可買,然后再來購買,商家的做法就是張貼廣告海報。 WebService也一樣,WebService客戶端要調用一個WebService服務,首先要有知道這個服務的地址在哪,以及這個服務里有什么方 法可以調用,所以,WebService務器端首先要通過一個WSDL文件來說明自己家里有啥服務可以對外調用,服務是什么(服務中有哪些方法,方法接受 的參數是什么,返回值是什么),服務的網絡地址用哪個url地址表示,服務通過什么方式來調用。
WSDL(Web Services Description Language)就是這樣一個基於XML的語言,用於描述Web Service及其函數、參數和返回值。它是WebService客戶端和服務器端都 能理解的標准格式。因為是基於XML的,所以WSDL既是機器可閱讀的,又是人可閱讀的,這將是一個很大的好處。一些最新的開發工具既能根據你的 Web service生成WSDL文檔,又能導入WSDL文檔,生成調用相應WebService的代理類代碼。
WSDL 文件保存在Web服務器上,通過一個url地址就可以訪問到它。客戶端要調用一個WebService服務之前,要知道該服務的WSDL文件的地址。 WebService服務提供商可以通過兩種方式來暴露它的WSDL文件地址:1.注冊到UDDI服務器,以便被人查找;2.直接告訴給客戶端調用者。
四、WebService知識小節
1、WebService是什么?
1.1 基於Web的服務:服務器端整出一些資源讓客戶端應用訪問(獲取數據)
1.2 一個跨語言、跨平台的規范(抽象)
1.3 多個跨平台、跨語言的應用間通信整合的方案(實際)
2、為什么要用 Web service?
web service能解決:跨平台調用 跨語言調用 遠程調用
3、什么時候使用web Service?
3.1. 同一家公司的新舊應用之間
3.2. 不同公司的應用之間
3.3. 一些提供數據的內容聚合應用:天氣預報、股票行情
4、Web Service中的幾個重要術語
4.1、WSDL(web service definition language)
WSDL是webservice定義語言, 對應.wsdl文檔, 一個webservice會對應一個唯一的wsdl文檔, 定義了客戶端與服務端發送請求和響應的數據格式和過程
4.2、SOAP(simple object access protocal)
SOAP是"簡單對象訪問協議"是一種簡單的、基於HTTP和XML的協議, 用於在WEB上交換結構化的數據
soap消息:請求消息和響應消息
4.3、SEI(WebService EndPoint Interface)
SEI是web service的終端接口,就是WebService服務器端用來處理請求的接口
4.4、CXF(Celtix + XFire)
一個apache的用於開發webservice服務器端和客戶端的框架。
五、WebService實戰
1.使用CXF開發WebService服務器端接口
CXF主頁:http://cxf.apache.org/
1.1首先建一個Maven的j2se項目;項目的jre用1.7,因為1.7有webservice的默認實現。不要用1.5 不然下面你用我的代碼會有問題,用1.5的話,還需要另外加jar包。
根據規范,我們先建一個接口類:HelloWorld
package com.pony1223.wsservice; import javax.jws.WebService; @WebService public interface HelloWorld { String say(String str); }
再建一個具體的實現類:HelloWorldImpl
package com.pony1223.wsservice.impl; import javax.jws.WebService; import com.pony1223.wsservice.HelloWorld; @WebService public class HelloWorldImpl implements HelloWorld { @Override public String say(String str) { return "Hello "+str; } }
最后建一個發布服務的主類:Server
package com.pony1223.test; import javax.xml.ws.Endpoint; import com.pony1223.wsservice.HelloWorld; import com.pony1223.wsservice.impl.HelloWorldImpl; public class Server { public static void main(String[] args) { System.out.println("start......"); HelloWorld hw = new HelloWorldImpl(); String address = "http://192.168.31.151:1122/helloworld"; Endpoint.publish(address, hw); System.out.println("started......"); } }
這里的Endpoint是Jdk自身實現的WebService。所以到這里我們不需要用到CXF的任何東西。
這里的address,寫上自己的本機IP
我們運行下Server類:
運行效果如下:
服務已發布成功。
下面我們介紹使用CXF來實現webservice接口:
我們先在pom.xml中加入:
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-core</artifactId> <version>3.1.5</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>3.1.5</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>3.1.5</version> </dependency>
這里要額外加入jetty,作為webservice發布的服務器。jetty是一個內嵌的web服務器
我們把Server改下。換成CXF實現:
import org.apache.cxf.jaxws.JaxWsServerFactoryBean; import com.java1234.webservice.impl.HelloWorldImpl; public class Server { public static void main(String[] args) { System.out.println("web service start"); HelloWorld implementor = new HelloWorldImpl(); String address = "http://192.168.1.103/helloWorld"; // Endpoint.publish(address, implementor); // JDK實現 JaxWsServerFactoryBean factoryBean = new JaxWsServerFactoryBean(); factoryBean.setAddress(address); // 設置暴露地址 factoryBean.setServiceClass(HelloWorld.class); // 接口類 factoryBean.setServiceBean(implementor); // 設置實現類 factoryBean.create(); System.out.println("web service started"); } }
運行效果和剛才一樣
2.使用CXF開發WebService客戶端
前面一講開發了webservice服務器端接口,今天的話,我們來開發webservice客戶端,讓大家來體驗下過程;
首先建一個Maven項目,項目名字,WS_Client;
然后我們要用CXF給我們提供的工具wsdl2java 來根據請求的url生成客戶端代碼;
wsdl2java工具在CXF開發包里;開發下載地址:http://cxf.apache.org/download.html
下載二進制包,然后解壓到D盤 ,wsdl2java命令;當然要用的話,還得配置Path。我們打開環境變量配置,加入路徑 D:\apache-cxf-3.1.5\bin 可能你的和我不一樣;
現在我們要干的事是在我們項目里生成我們需要的webservice客戶端代碼,
我們找到項目的本地路徑,
然后我們進入dos,進入上面的本地硬盤地址,然后執行命令:wsdl2java http://192.168.1.103/helloWorld?wsdl
這樣就完成了代碼的生成,我們刷新下工程:最關鍵的代碼是HelloWorldService.java 我們下面寫請求主類要用到;
我們下面寫下主類 Client ,自己建下:
public class Client { public static void main(String[] args) { HelloWorldService service=new HelloWorldService(); HelloWorld helloWorld=service.getHelloWorldPort(); System.out.println(helloWorld.say("java")); } }
運行后即可調用服務端代碼。
3.CXF處理JavaBean以及復合類型
前面講的是處理簡單類型,今天這里來講下CXF處理JavaBean以及復合類型,比如集合;
這里實例是客戶端傳一個JavaBean,服務器端返回集合類型;
在原來的項目實例基礎上,我們先創建一個實體類User:
/** * 用戶實體類 * @author Administrator * */ public class User { private Integer id; // 編號 private String userName; // 用戶名 private String password; // 密碼 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } 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; } }
再創建一個Role實體類:
/** * 角色實體 * @author Administrator * */ public class Role { private Integer id; // 編號 private String roleName; // 角色名稱 public Role() { super(); // TODO Auto-generated constructor stub } public Role(Integer id, String roleName) { super(); this.id = id; this.roleName = roleName; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } }
然后HelloWorld再加一個接口方法getRoleByUser,通過用戶查找角色:
import java.util.List; import javax.jws.WebService; import com.java1234.entity.Role; import com.java1234.entity.User; @WebService public interface HelloWorld { public String say(String str); public List<Role> getRoleByUser(User user); }
然后HelloWorld接口實現類 HelloWorldImpl寫下新增的方法的具體實現,我們這里寫死,模擬下即可:
import java.util.ArrayList; import java.util.List; import javax.jws.WebService; import com.java1234.entity.Role; import com.java1234.entity.User; import com.java1234.webservice.HelloWorld; @WebService public class HelloWorldImpl implements HelloWorld{ public String say(String str) { return "Hello "+str; } public List<Role> getRoleByUser(User user) { List<Role> roleList=new ArrayList<Role>(); // 模擬 直接寫死 if(user!=null){ if(user.getUserName().equals("java1234") && user.getPassword().equals("123456")){ roleList.add(new Role(1,"技術總監")); roleList.add(new Role(2,"架構師")); }else if(user.getUserName().equals("jack") && user.getPassword().equals("123456")){ roleList.add(new Role(3,"程序員")); } return roleList; }else{ return null; } } }
服務端其他地方不用動;客戶端代碼從新生成。改下Client類:
import java.util.List; public class Client { public static void main(String[] args) { HelloWorldService service=new HelloWorldService(); HelloWorld helloWorld=service.getHelloWorldPort(); //System.out.println(helloWorld.say("java1234")); User user=new User(); user.setUserName("jack"); user.setPassword("123456"); List<Role> roleList=helloWorld.getRoleByUser(user); for(Role role:roleList){ System.out.println(role.getId()+","+role.getRoleName()); } } }
運行截圖:
4.CXF處理一些Map等復雜類型
前面講的一些都是簡單類型,cxf都支持。但是有些復雜類型,cxf是不支持,比如常用的Map類型;
下面我們在前面的實例基礎上在加一個方法,比如我們現在有個需求,獲取所有用用戶以及對應的每個用戶所有角色信息;
服務器端:
HelloWorld接口加方法:
/** * 獲取所有用戶以及對應的角色 * @return */ public Map<String,List<Role>> getRoles();
HelloWorldImpl實現類加方法實現:
public Map<String, List<Role>> getRoles() { Map<String,List<Role>> map=new HashMap<String,List<Role>>(); List<Role> roleList1=new ArrayList<Role>(); roleList1.add(new Role(1,"技術總監")); roleList1.add(new Role(2,"架構師")); map.put("java1234", roleList1); List<Role> roleList2=new ArrayList<Role>(); roleList2.add(new Role(1,"程序員")); map.put("jack", roleList2); return map; }
然后我們啟動Server類:發現報錯:
這個報錯信息說,不支持該類型;
這里我們有好幾種解決方案,這里我們用最常用的一種,使用適配器,把cxf不能接受的類型通過適配器,轉能接受的類型。
我們使用@XmlJavaTypeAdapter注解,加在接口定義上,完整接口代碼如下:
@WebService public interface HelloWorld { public String say(String str); public List<Role> getRoleByUser(User user); /** * 獲取所有用戶以及對應的角色 * @return */ @XmlJavaTypeAdapter(MapAdapter.class) public Map<String,List<Role>> getRoles(); }
這里參數需要一個實現了XmlAdapter類的適配器類;
/** * Map適配器 * @author Administrator * */ public class MapAdapter extends XmlAdapter<MyRole[], Map<String,List<Role>>>{ /** * 適配轉換 MyRole[] -> Map<String, List<Role>> */ @Override public Map<String, List<Role>> unmarshal(MyRole[] v) throws Exception { Map<String, List<Role>> map=new HashMap<String,List<Role>>(); for(int i=0;i<v.length;i++){ MyRole r=v[i]; map.put(r.getKey(), r.getValue()); } return map; } /** * 適配轉換 Map<String, List<Role>> -> MyRole[] */ @Override public MyRole[] marshal(Map<String, List<Role>> v) throws Exception { MyRole[] roles=new MyRole[v.size()]; int i=0; for(String key:v.keySet()){ roles[i]=new MyRole(); roles[i].setKey(key); roles[i].setValue(v.get(key)); i++; } return roles; } }
這里的話XmlAdapter要加兩個參數,XmlAdapter<ValueType,BoundType>
ValueType是cxf能接收的類型,這里我用了數組;
BoundType是cxf不能接受的類型,也就是我例子里的需求的Map類型;
這里大家會看到,還有一個MyRole自定義類型,key:value。我們搞成兩個屬性,具體實現如下:
/** * 自定義實體 cxf能接受 * @author Administrator * */ public class MyRole { private String key; private List<Role> value; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public List<Role> getValue() { return value; } public void setValue(List<Role> value) { this.value = value; } }
OK 這樣就行了。我們運行Server類,發布webservice接口:
然后就到了webservice客戶端,我們用wsdl2java工具生成下最新代碼,具體過程前面講過,這里不重復講:修改下Client類:
public class Client { public static void main(String[] args) { HelloWorldService service=new HelloWorldService(); HelloWorld helloWorld=service.getHelloWorldPort(); //System.out.println(helloWorld.say("java1234")); /*User user=new User(); user.setUserName("jack"); user.setPassword("123456"); List<Role> roleList=helloWorld.getRoleByUser(user); for(Role role:roleList){ System.out.println(role.getId()+","+role.getRoleName()); }*/ MyRoleArray array=helloWorld.getRoles(); List<MyRole> roleList=array.item; for(int i=0;i<roleList.size();i++){ MyRole my=roleList.get(i); System.out.print(my.key+":"); for(Role r:my.value){ System.out.print(r.getId()+","+r.getRoleName()+" "); } System.out.println(); } } }
運行效果:
5.CXF添加攔截器
今天開始講下攔截器,前面大家學過servlet,struts2 都有攔截器概念,主要作用是做一些權限過濾,編碼處理等;
webservice也可以加上攔截器,我們可以給webservice請求加權限判斷功能;
webservice分服務端和客戶端,服務端和客戶端都是可以加攔截器的,無論是服務端還是客戶端,都分進,出(In,Out)攔截器;
我們先來改下服務端的Server類:
import org.apache.cxf.interceptor.LoggingInInterceptor; import org.apache.cxf.interceptor.LoggingOutInterceptor; import org.apache.cxf.jaxws.JaxWsServerFactoryBean; import com.java1234.webservice.impl.HelloWorldImpl; public class Server { public static void main(String[] args) { System.out.println("web service start"); HelloWorld implementor = new HelloWorldImpl(); String address = "http://192.168.1.103/helloWorld"; JaxWsServerFactoryBean factoryBean = new JaxWsServerFactoryBean(); factoryBean.setAddress(address); // 設置暴露地址 factoryBean.setServiceClass(HelloWorld.class); // 接口類 factoryBean.setServiceBean(implementor); // 設置實現類 factoryBean.getInInterceptors().add(new LoggingInInterceptor()); // 添加in攔截器 日志攔截器 factoryBean.getOutInterceptors().add(new LoggingOutInterceptor()); // 添加out攔截器 factoryBean.create(); System.out.println("web service started"); } }
這里的話,我們通過factoryBean對象可以獲取攔截器組,添加進或者出攔截器,這里有個經典的攔截器,我們開發的時候經常用,就是日志攔截器,
我們可以把客戶端的請求,以及服務端返回的信息打印出來,可以打印控制台,也可以打印到執行文件;這里為了演示方便,直接搞無參的攔截器,
打印到控制台;
無論是自定義的攔截器,還是CXF自帶的攔截器,都必須實現Interceptor接口。
同理,我們在客戶端也可以加進出攔截器,修改Client代碼:
import java.util.List; import org.apache.cxf.frontend.ClientProxy; import org.apache.cxf.interceptor.LoggingInInterceptor; import org.apache.cxf.interceptor.LoggingOutInterceptor; public class Client { public static void main(String[] args) { HelloWorldService service=new HelloWorldService(); HelloWorld helloWorld=service.getHelloWorldPort(); org.apache.cxf.endpoint.Client client=ClientProxy.getClient(helloWorld); client.getInInterceptors().add(new LoggingInInterceptor()); // 添加in攔截器 日志攔截器 client.getOutInterceptors().add(new LoggingOutInterceptor()); // 添加out攔截器 //System.out.println(helloWorld.say("java1234")); /*User user=new User(); user.setUserName("jack"); user.setPassword("123456"); List<Role> roleList=helloWorld.getRoleByUser(user); for(Role role:roleList){ System.out.println(role.getId()+","+role.getRoleName()); }*/ MyRoleArray array=helloWorld.getRoles(); List<MyRole> roleList=array.item; for(int i=0;i<roleList.size();i++){ MyRole my=roleList.get(i); System.out.print(my.key+":"); for(Role r:my.value){ System.out.print(r.getId()+","+r.getRoleName()+" "); } System.out.println(); } } }
這里的話,我們用到了ClientProxy,客戶端代理。
6.CXF添加自定義攔截器
前面我們說到CXF添加內置的攔截器,今天的話,我們來講下如何添加自定義攔截器;
我們的實例是客戶端訪問服務端webservice接口要加權限認證。
我們思路先說下。我們可以通過在SOAP消息的Header頭信息中添加自定義信息,然后發送到服務端端,服務器端通過獲取
Header頭消息,然后進行認證;這里的添加消息,和獲取消息認證,我們都是通過自定義攔截器來實現;
OK下面我們來實現下:
首先是服務器端:
我們自定義攔截器:MyInterceptor
/** * 自定義攔截器 * @author Administrator * */ public class MyInterceptor extends AbstractPhaseInterceptor<SoapMessage>{ public MyInterceptor(){ // 在調用方法之前調用攔截器 super(Phase.PRE_INVOKE); } /** * 攔截獲取消息 */ public void handleMessage(SoapMessage message) throws Fault { List<Header> headers=message.getHeaders(); if(headers==null || headers.size()==0){ throw new Fault(new IllegalArgumentException("沒有Header,攔截器實施攔截")); } Header firstHeader=headers.get(0); Element ele=(Element) firstHeader.getObject(); NodeList userIds=ele.getElementsByTagName("userName"); NodeList userPasses=ele.getElementsByTagName("password"); if(userIds.getLength()!=1){ throw new Fault(new IllegalArgumentException("用戶名格式不對")); } if(userPasses.getLength()!=1){ throw new Fault(new IllegalArgumentException("密碼格式不對")); } String userId=userIds.item(0).getTextContent(); String userPass=userPasses.item(0).getTextContent(); if(!userId.equals("java1234") || ! userPass.equals("123456")){ throw new Fault(new IllegalArgumentException("用戶名或者密碼不正確")); } } }
這里的話,我們主要是獲取Header頭消息,然后獲取userName和password節點,然后獲取值,進行權限判斷,假如認證不通過,我們拋出異常;
在Server類里,我們要添加一個in 攔截器,在進入的時候,我們要進行驗證;
public class Server { public static void main(String[] args) { System.out.println("web service start"); HelloWorld implementor = new HelloWorldImpl(); String address = "http://10.10.7.18/helloWorld"; JaxWsServerFactoryBean factoryBean = new JaxWsServerFactoryBean(); factoryBean.setAddress(address); // 設置暴露地址 factoryBean.setServiceClass(HelloWorld.class); // 接口類 factoryBean.setServiceBean(implementor); // 設置實現類 factoryBean.getInInterceptors().add(new LoggingInInterceptor()); // 添加in攔截器 日志攔截器 factoryBean.getOutInterceptors().add(new LoggingOutInterceptor()); // 添加out攔截器 factoryBean.getInInterceptors().add(new MyInterceptor()); // 添加自定義攔截器 factoryBean.create(); System.out.println("web service started"); } }
接下來是修改客戶端代碼:
我們同樣要添加一個自定義攔截器:AddHeaderInterceptor
public class AddHeaderInterceptor extends AbstractPhaseInterceptor<SoapMessage> { private String userName; private String password; public AddHeaderInterceptor(String userName, String password) { super(Phase.PREPARE_SEND); // 發送SOAP消息之前調用攔截器 this.userName=userName; this.password=password; } public void handleMessage(SoapMessage message) throws Fault { List<Header> headers=message.getHeaders(); Document doc=DOMUtils.createDocument(); Element ele=doc.createElement("authHeader"); Element idElement=doc.createElement("userName"); idElement.setTextContent(userName); Element passElement=doc.createElement("password"); passElement.setTextContent(password); ele.appendChild(idElement); ele.appendChild(passElement); headers.add(new Header(new QName("java1234"),ele)); }
這里的話,我們主要是在攔截器里創建頭消息;
Client類里我們要修改下,加下Out 攔截器:
public class Client { public static void main(String[] args) { HelloWorldService service=new HelloWorldService(); HelloWorld helloWorld=service.getHelloWorldPort(); org.apache.cxf.endpoint.Client client=ClientProxy.getClient(helloWorld); // client.getInInterceptors().add(new LoggingInInterceptor()); // 添加in攔截器 日志攔截器 client.getOutInterceptors().add(new AddHeaderInterceptor("java1234","123456")); // 添加自定義攔截器 client.getOutInterceptors().add(new LoggingOutInterceptor()); // 添加out攔截器 //System.out.println(helloWorld.say("java1234")); /*User user=new User(); user.setUserName("jack"); user.setPassword("123456"); List<Role> roleList=helloWorld.getRoleByUser(user); for(Role role:roleList){ System.out.println(role.getId()+","+role.getRoleName()); }*/ MyRoleArray array=helloWorld.getRoles(); List<MyRole> roleList=array.item; for(int i=0;i<roleList.size();i++){ MyRole my=roleList.get(i); System.out.print(my.key+":"); for(Role r:my.value){ System.out.print(r.getId()+","+r.getRoleName()+" "); } System.out.println(); } } }
7.Spring整合CXF之發布WebService服務
今天我們來講下如何用Spring來整合CXF,來發布WebService服務;
給下官方文檔地址:http://cxf.apache.org/docs/writing-a-service-with-spring.html
根據官方文檔。我們把前面的實例用Spring整合CXF來處理下。會簡化很多;
首先我們來建一個Maven項目 WebService_CXF
建好項目第一步,我們打開pom.xml
我們來添加下Spring支持:
<!-- 添加Spring支持 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.1.7.RELEASE</version> </dependency> 接下來添加下CXF支持: <!-- 添加cxf支持 --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-core</artifactId> <version>3.1.5</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>3.1.5</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>3.1.5</version> </dependency>
我們在項目里添加下 applicationContext.xml spring配置文件 我們要額外添加下命名路徑,因為我們要用新的標簽;
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
這里的我是參考官方文檔上,添加了 jaxws支持。。大家直接貼下即可;
然后我們再導入下cxf里的一些bean配置,參考官方文檔:
<import resource="classpath:META-INF/cxf/cxf.xml"/> <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
這里的HelloWorldImpl類上,我們加一個 @Component("helloWorld")
Spring配置文件里,我加下掃描:
<!-- 自動掃描 --> <context:component-scan base-package="com.java1234.webservice" />
前面搞完后,我們在處理下web.xml文件 首先啟動的時候,必須加載Spring:
<!-- Spring配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- Spring監聽器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
然后我們要定義一個Servlet,主要是處理WebService請求:
<servlet> <servlet-name>CXFServlet</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/webservice/*</url-pattern> </servlet-mapping>
這里的話,我們所有的 /webservice請求,都交給CXFServlet類處理;
最后一步,我們在Spring配置文件里,定義下webservice接口發布:
<!-- 定義服務提供者 --> <jaxws:endpoint implementor="#helloWorld" address="/HelloWorld" ></jaxws:endpoint>
這里implementor指定webservice接口實現類
address是具體的接口路徑
最終完整的applicationContext.xml配置文件如下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.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-servlet.xml"/> <!-- 自動掃描 --> <context:component-scan base-package="com.java1234.webservice" /> <!-- 定義服務提供者 --> <jaxws:endpoint implementor="#helloWorld" address="/HelloWorld" ></jaxws:endpoint> </beans>
我們來啟動下項目,然后訪問 http://localhost:8080/WebService_CXF/webservice/
8.Spring整合CXF之添加攔截器
<!-- 定義服務提供者 --> <jaxws:endpoint implementor="#helloWorld" address="/HelloWorld"> <!-- 添加in攔截器 --> <jaxws:inInterceptors> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/> <bean class="com.java1234.interceptor.MyInterceptor"/> </jaxws:inInterceptors> <!-- 添加out攔截器 --> <jaxws:outInterceptors> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/> </jaxws:outInterceptors> </jaxws:endpoint>
參考資料:
http://www.cnblogs.com/xdp-gacl
java1234