我們知道,大多數情況下,方法的調用都是發生在相同堆上的兩個對象之間,所有組件都在同一臺計算機的同一個Java虛擬機的同一個堆空間上執行是最簡單的,如果用戶端只是個能夠執行Java的裝置怎麼辦?如果爲了安全性的理由只能讓服務器上的程序存取數據庫怎麼辦?例如,我們的BS系統、單純的CS系統等等,但是如果我們有這樣的需求:我們的項目需要調用不同機器上的對象和方法,我們應該怎麼處理呢,例如下面的案例:
大家都知道,我們的web項目在服務器上部署完後,我們可以通過在瀏覽器上訪問地址的方式進行訪問,也可以通過在地址欄輸入地址的方式執行裏面的方法:http://localhost:8080/test/query!queryUser.do,然後在頁面上處理得到我們相應的結果,如果我們的客戶端不是使用瀏覽器訪問,而是通過swing程序或者其他非瀏覽器訪問方式,我們要在客戶端程序中獲取服務器裏面相應的方法,得到相應的信息後,再在我們的客戶端做出相應的邏輯處理,我們的客戶端不與數據庫打交道,無需操心將數據發送到網絡上或者解析響應之類的問題,我們該如何進行處理呢?
這就用到了我們的一種遠程調用機制,我們從某一臺計算機上面取得另一臺計算機上的信息是通過socket的輸入/輸出流,打開另一臺計算機的socket連接,然後取得outputStream來寫入數據.但如果要調用另一臺計算機上,另一個Java虛擬機上面的對象的方法,我們當然可以自己定義和設計通信協議來調用,然後通過Socket把執行結果再傳回去,並且還能夠像是對本機的方法調用一樣,也就是說想要調用遠程的對象(像是別的堆上的),卻又要像是一般的調用,這就是分佈式的服務調用,在分佈式服務框架中,一個最基礎的問題就是遠程服務是怎麼通訊的,在Java底層領域中有很多可實現遠程通訊的技術,例如:RMI、MINA、ESB、Burlap、SOAP、EJB和JMS 等,在j2ee中,對java底層遠程通訊的技術進行了封裝,形成了 Hessian 、 HttpInvoker 、 XFire 、 Axis 等多種形式的遠程調用技術。但對高級程序員而言仍需要掌握Java底層領域中遠程通訊的技術,尤其是rmi,xml-rpc,JMS。下面我來給大家講解一下由caucho提供的一個基於binary-RPC實現的遠程通訊library的Hessian。
Hessian是一個輕量級的remoting onhttp工具,使用簡單的方法提供了RMI的功能。 相比WebService,Hessian更簡單、快捷。採用的是二進制RPC協議,因爲採用的是二進制協議,所以它很適合於發送二進制數據。
<!--以上很大部分摘自網絡,自己整合-->
說到底,就是我們要通過一臺機器調用另一臺機器上、另一個java虛擬機上的對象和方法,我們可以通過輕量級的遠程通訊Hessian來實現,下面我們來搭建一個簡單的helloworld:
使用Hessian,需要導入Hessian的一個支持包,可以在它的官網進行下載:http://hessian.caucho.com,也可以在我的資源庫裏面進行免費下載:http://download.csdn.net/detail/harderxin/7125443
好了,下面讓我們開始使用Hessian,我要實現的功能是,我的客戶端能夠通過main方法調用服務器端的方法,並且將服務器端執行的結果返回給客戶端打印出來:
一、新建web項目,我取名爲HessianServer,然後將hessian-4.0.37.jar導入我們的工程中,然後建立我們的實體類User,因爲我客戶端就是要獲取User信息,給他定義id、name、password屬性:
package com.server.bean; import java.io.Serializable; public class User implements Serializable{ /** * */ private static final long serialVersionUID = 7175134832651443717L; //用戶編號 private int id; //用戶名 private String userName; //密碼 private String password; public int getId() { return id; } public void setId(int 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; } public User(int id, String userName, String password) { super(); this.id = id; this.userName = userName; this.password = password; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; result = prime * result + ((password == null) ? 0 : password.hashCode()); result = prime * result + ((userName == null) ? 0 : userName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; User other = (User) obj; if (id != other.id) return false; if (password == null) { if (other.password != null) return false; } else if (!password.equals(other.password)) return false; if (userName == null) { if (other.userName != null) return false; } else if (!userName.equals(other.userName)) return false; return true; } }
定義一個UserService接口,用來處理User邏輯,也是得交給客戶端去動態代理的:
package com.server.service; import java.util.List; import com.server.bean.User; public interface UserService { public List<User> getUser(); }
定義UserService的實現類UserServiceImpl,該類實現UserService,並處理相應的邏輯:
package com.server.service.impl; import java.util.ArrayList; import java.util.List; import com.server.bean.User; import com.server.service.UserService; public class UserServiceImpl implements UserService{ public List<User> getUser() { //我們可以在這個方法中與數據庫打交道 List<User> list=new ArrayList<User>(); list.add(new User(1,"Mary","123456")); list.add(new User(2,"Jack","236547")); list.add(new User(3,"Joy","362541")); return list; } }
二、配置好web.xml,將Hessian與我們的項目進行整合:
<?xml version="1.0" encoding="UTF-8"?> <web-app 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"> <servlet> <servlet-name>UserService</servlet-name> <!-- 配置HessianServlet--> <servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class> <init-param> <param-name>home-class</param-name> <!-- 我們定義的接口實現類 --> <param-value>com.server.service.impl.UserServiceImpl</param-value> </init-param> <init-param> <param-name>home-api</param-name> <!-- 我們定義的接口類 --> <param-value>com.server.service.UserService</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>UserService</servlet-name> <!-- 客戶端訪問路徑 --> <url-pattern>/us</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
三、將User和UserService打成的jar包,讓客戶端能夠得到服務端的實體和接口,我們也可以不用這樣做,但是我們得需要把服務器端的User和UserService類再複製到客戶端,所以做成jar包方便我們的訪問,我打的jar包名稱爲:project-1.0.jar
好了,我們的服務端弄好了,下面我們將服務端部署到Tomcat服務器上,然後啓動tomcat服務器,我們程序的訪問地址爲:http://localhost:8080/HessianServer/us
四、編寫我們的客戶端,我取名爲HessianClient,將hessian-4.0.37.jar和我們剛剛導出的project.1.0.jar導入到我們的工程中,新建一個UserServiceTest的java類:
package com.client.test; import java.net.MalformedURLException; import java.util.List; import com.caucho.hessian.client.HessianProxyFactory; import com.server.bean.User; import com.server.service.UserService; public class UserServiceTest { public static void main(String[] args) { //我們的服務器訪問的地址 String url="http://localhost:8080/HessianServer/us"; //獲得HessianProxyFactory實例 HessianProxyFactory factory=new HessianProxyFactory(); try { //創建我們的接口對象 UserService userService=(UserService)factory.create(url); //執行服務端方法 List<User> users=userService.getUser(); //遍歷輸出 for(User user:users){ System.out.println("id="+user.getId()+",name="+user.getUserName()+",pwd="+user.getPassword()); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
執行main方法,打印出來得到的結果爲:
id=1,name=Mary,pwd=123456
id=2,name=Jack,pwd=236547
id=3,name=Joy,pwd=362541
通過Hessian,我們成功的從我們的客戶端(PC機1)訪問到了服務器(PC機2)上的方法和對象,兩個程序的運行在兩個不同的java虛擬機上面,這就是一個很簡單的遠程通訊,下面來帶大家更多的瞭解Hessian:
1、是基於什麼協議實現的?
基於Binary-RPC協議實現。
2、怎麼發起請求?
需通過Hessian本身提供的API來發起請求。
3、怎麼 將請求轉化爲符合協議的格式的?
Hessian通過其自定義的串行化機制將請求信息進行序列化,產生二進制流。
4、使用什麼 傳輸協議傳輸?
Hessian基於Http協議進行傳輸。
5、響應端基於什麼機制來接收請求?
響應端根據Hessian提供的API來接收請求。
6、怎麼將流還原爲傳輸格式的?
Hessian根據其私有的串行化機制來將請求信息進行反序列化,傳遞給使用者時已是相應的請求信息對象了。
7、處理完畢後怎麼迴應?
處理完畢後直接返回,hessian將結果對象進行序列化,傳輸至調用端。
簡單的原理圖:
1)Hessian遠程訪問基於序列化和反序列化的方式。當程序運行時,程序所創建的各種對象都位於內存中,當程序運行結束,這些對象就結束了生命週期。對象的序列化主要有兩種用途:
l 把對象的字節序列永久地保存到硬盤上,通常是放在一個文件中。
l 在網絡上傳輸對象的字節序列
Hessian就是把Java對象轉變成 字節序列,然後通過Http傳輸到 目標服務器上,目標服務器收到這個字節序列後,按照一定的協議標準進行反序列,提交給對應的服務處理。處理完成以後以同樣的方式返回數據\
2)配置的Servlet: com.caucho.hessian.server.HessianServlet
對應的參數:接口(home-api):com.alisoft.enet.hessian.Hello
實現(home-class): com.alisoft.enet.hessian.HelloImpl
HessianServlet 中的實現代碼如下(略過部分代碼):
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
InputStream is = request.getInputStream();
OutputStream os = response.getOutputStream();
//輸入流
Hessian2Input in = new Hessian2Input(is);
SerializerFactory serializerFactory = getSerializerFactory();
in.setSerializerFactory(serializerFactory);
//輸出流
AbstractHessianOutput out;
int major = in.read();
int minor = in.read();
out = new Hessian2Output(os);
out.setSerializerFactory(serializerFactory);
_homeSkeleton.invoke(in, out);
整個執行步驟如下:
l 接收輸入流,並通過SerializerFactory轉化爲 Hessian 特有的 Hessian2Input
l 設置輸出流,並通過SerializerFactory轉化爲 Hessian 特有的 Hessian2Output
l 根據配置的接口和實現參數,調用服務,並把結果寫入到輸出流 Hessian2Output中
l Out.close()
3)Hessian 和其他遠程調用實現的比較
1. 常見遠程通訊協議:
RMI 、 Httpinvoker 、 Hessian 、 Burlap 、 Web service
通訊效率測試結果:
RMI > Httpinvoker >= Hessian >> Burlap >> Web service
2. 各個通訊協議的分析:
RMI 是 Java 首選遠程調用協議,非常高效穩定,特別是在數據結構複雜,數據量大的情況下,與其他通訊協議的差距尤爲明顯。
HttpInvoker 使用 java 的序列化技術傳輸對象,與 RMI 在本質上是一致的。從效率上看,兩者也相差無幾, HttpInvoker 與 RMI 的傳輸時間基本持平。
Hessian 在傳輸少量對象時,比 RMI 還要快速高效,但傳輸數據結構複雜的對象或大量數據對象時,較 RMI 要慢 20% 左右。但這只是在數據量特別大,數據結構很複雜的情況下才能體現出來,中等或少量數據時, Hessian 並不比 RMI 慢。 Hessian 的好處是精簡高效,可以跨語言使用,而且協議規範公開,我們可以針對任意語言開發對其協議的實現。
另外, Hessian 與 WEB 服務器結合非常好,藉助 WEB 服務器的成熟功能,在處理大量用戶併發訪問時會有很大優勢,在資源分配,線程排隊,異常處理等方面都可以由成熟的 WEB 服務器保證。而 RMI 本身並不提供多線程的服務器。而 且, RMI 需要開防火牆端口, Hessian 不用。
Burlap 採用 xml 格式傳輸。僅在傳輸 1 條數據時速度尚可,通常情況下,它的毫時是 RMI 的 3 倍。
Web Service 的效率低下是衆所周知的,平均來看, Web Service 的通訊毫時是 RMI 的 10 倍。
至於RMI、Web service不瞭解的可以自己在網上下載相應的文檔進行查看學習
4)分佈式編程的定義和理解:
基本思想
所有分佈編程技術的基本思想都很簡單:
1.客戶端通過網絡將請求發送到服務端.
2.服務端處理請求返回相應.
3.客戶端完成後續處理.
定義聲明:
這些請求和響應不是在Web 應用程序中看到的請求和響應,這裏的客戶端並非是Web瀏覽器,它可以是執行具有任意複雜度業務規則的任何應用程序。(例如Swing用戶界面等等)用於請求和響應數據的協議允許傳遞任意對象,而傳統的Web應用程序只限於對請求使用Http協議,對響應使用HTML.
我們需要什麼樣的分佈式模式?
開發中我們正真需要的這樣一種機制:客戶端程序員以常規的方式進行方法調用,而無需操心將數據發送到網絡上或者解析響應之類的問題。解決方法:在客戶端爲遠程對象安裝一個代理(Proxy)。代理是位於客戶端虛擬機中的一個對象,對於客戶端程序來說,看起來就像是要訪問的遠程對象一樣。客戶調用此代理時,只需進行常規的方法調用。而客戶端代理負責使用網絡協議與服務進行聯繫。 同樣,服務端的程序員也不希望因與客戶端之間的通信被絆住。解決方法:在服務端也安裝一個代理。該服務端代理與客戶端代理進行通信,並且它將以常規方式調用服務器對象上的方法。
代理之間是如何通信的呢?通常有三種選擇:
Corba:通用的對象請求代理架構,支持任何語言編寫對象之間的方法調用。
Corba使用二進制的Internet Inter- ORB協議(IIOP)來實現對象間的通信.
Web:Web服務架構是一個協議集,有時統一描述爲WS-*它也是獨立於編程語言的,不過它使用基於XML的通信格式.用於傳輸對象的格式則是簡單對象訪問協議.
RMI:Java的遠程方法調用技術,支持Java的分佈式對象之間的方法調用.
分佈式編程的關鍵是遠程方法調用。在一臺計算機上(客戶端)上的某些代碼希望調用另一臺計算機(服務端)上的某個對象的一個方法.要實現這一點,方法的參數必須以某種方式傳遞到另外一臺機器上,而服務器必須得到通知,去定位遠程對象並執行要調用的方法,並且必須將返回值傳遞回去.
方法調用的關鍵元素:存根與參數編組.當客戶端代碼要在遠程對象上調用一個遠程方法時,實際上調用的是代理對象上的一個普通的方法,我們稱此代理對象爲存根(stub).
存根位於客戶端機器上,而非服務器上,它知道如何通過網絡與服務器聯繫。存根會將遠程方法所需的參數打包成一組字節。對參數編碼的過程稱作參數編組,參數編組的目的是將參數轉換爲適合在虛擬機之間進行傳遞的格式.獲取客戶端存根的細節依賴採用的具體分佈式技術.
Hessian和Spring、hibernate、Struts整合見下篇博文!歡迎一起交流學習