基於 Hessian 輕量級遠端呼叫的原理及示例
1 簡介
Hessian 是 Caucho 公司開發的一種基於二進位制 RPC 協議(Remote Procedure Call protocol)的輕量級遠端呼叫框架,其使用簡單的方法提供了 RMI 的功能。 相比 WebService,Hessian 更簡單、快捷,它主要包括 Hessian 遠端呼叫協議、Hessian 序列化協議和客戶端服務端代理。特別提示,Hessian 遠端呼叫框架是構建在 HTTP 協議之上的。
2 呼叫過程
其中,步驟 3、4、5、6 為核心過程,在此進行更深層次的解析,
步驟 3:將遠端方法呼叫轉換為 Hessian 呼叫,具體為,客戶端首先要先和伺服器端建立 Socket 連線,然後傳送 HTTP 請求,Hessian 遠端呼叫將經過 Hessian 序列化的引數等二進位制資料作為 HTTP 請求的資料部分被提交到服務端,並且目前只支援 Post 提交方法。
步驟 4:將 Hessian 呼叫轉換為本地方法呼叫,步驟 3 裡請求的 url 是暴露服務時對映好的,也即指定的服務端代理物件會解析客戶端服務代理物件進行的 Hessian 遠端呼叫,然後反序列化引數,找到被代理的服務類(暴露服務時指定的服務類),通過反射呼叫服務類中的相應服務方法。
步驟 5:返回遠端呼叫返回值給服務呼叫者,步驟 4 裡呼叫服務類的方法會返回具體值,經過 Hessian 序列化後作為 Hessian 呼叫的返回值,被放在 HTTP 響應的 body 部分被返回給客戶端。
步驟 6:客戶端代理物件解析 body 部分 Hessian 呼叫返回 reply,解析出遠端呼叫返回值再反序列化,最終得到結果。
3 注意事項
在進行基於 Hessian 協議的專案開發時,構建的服務端和客戶端應該具備如下內容,
服務端:
- 包含 Hessian 的 jar 包;
- 設計一個介面,用來給客戶端呼叫;
- 實現該介面的功能;
- 配置
web.xml
,配好相應的 Servlet; - 對於複雜物件可以使用 Map 的方法傳遞;
- 由於使用二進位制 RPC 協議傳輸資料,物件必須進行序列化,實現 Serializable 介面。
客戶端:
- 包含 Hessian 的 jar 包;
- 具有和伺服器端結構一樣的介面;
- 利用 HessianProxyFactory 呼叫遠端介面。
Hessian 的 jar 包可以通過下面提供的兩個地址下載:
4 呼叫示例
4.1 示例一
新建一個 Web Project 專案,將 Hessian 的 jar 包匯入到External Libraries
中:
/**
* 建立介面
*/
package com.hit;
public interface BasicAPI {
public void setGreeting(String greeting);
public String hello();
public User getUser();
}
/**
* 實現介面
*/
package com.hit;
public class BasicService implements BasicAPI {
private String _greeting = "Hello, hessian";
public void setGreeting(String greeting) {
_greeting = greeting;
System.out.println("Set greeting success:" + _greeting);
}
public String hello(){
return _greeting;
}
public User getUser() {
return new User("charies", "xiaomima");
}
}
/**
* 建立一個實現 Serializable 介面的 POJO 類(簡單的 Java 類),也可以說是 Bean
*/
package com.hit;
import java.io.Serializable;
public class User implements Serializable {
String userName = "charies";
String password = "xiaomima";
public User(String user, String pwd) {
this.userName = user;
this.password = pwd;
}
public String getUserName() {
return userName;
}
public String getPassword() {
return password;
}
}
<!-- 配置 web.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>Hessian</display-name>
<servlet>
<servlet-name>helloHssian</servlet-name>
<servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>
<init-param>
<param-name>service-class</param-name>
<param-value>com.hit.BasicService</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>helloHssian</servlet-name>
<url-pattern>/helloHssian</url-pattern>
</servlet-mapping>
</web-app>
接下來,再建立一個 Java Project,匯入 Hessian 的 jar 包,並建立與伺服器端一樣的介面及基礎類:
package com.hit;
public interface BasicAPI{
public void setGreeting(String greeting);
public String hello();
public User getUser();
}
package com.hit;
import java.io.Serializable;
public class User implements Serializable{
String userName ="charies";
String password ="xiaomima";
public User(String user, String pwd) {
this.userName = user;
this.password = pwd;
}
public String getUserName() {
return userName;
}
public String getPassword() {
return password;
}
}
package com.hit;
import com.caucho.hessian.client.HessianProxyFactory;
public class BasicClient {
public static void main(String[] args) throws Exception {
String url ="http://localhost:8080/Hessian/helloHessian";
HessianProxyFactory factory = new HessianProxyFactory();
BasicAPI basic = (BasicAPI) factory.create(BasicAPI.class, url);
System.out.println("Hello:" + basic.hello());
System.out.println("Hello:" + basic.getUser().getUserName());
System.out.println("Hello:" + basic.getUser().getPassword());
basic.setGreeting("HelloGreeting");
System.out.println("Hello:" + basic.hello());
}
}
4.2 示例二
建立服務端:
/**
* 建立介面
*/
package com.hit.hessian.service;
public interface Basic {
public String sayHello();
}
/**
* 實現介面
*/
package com.hit.hessian.service;
public class BasicService implements Basic {
private String message = "We are champion!";
@Override
public String sayHello() {
return message;
}
}
/**
* 實現介面並繼承 HessianServlet
*/
package com.hit2.hessian.service;
import com.caucho.hessian.server.HessianServlet;
public class BasicServiceAlso extends HessianServlet implements Basic{
private String message = "you you, qie ke nao!";
@Override
public String sayHello() {
return message;
}
}
<!-- 配置 web.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>HessianWeb</display-name>
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>
<init-param>
<param-name>home-class</param-name>
<param-value>com.hit.hessian.service.BasicService</param-value>
</init-param>
<init-param>
<param-name>home-api</param-name>
<param-value>com.hit.hessian.service.Basic</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/helloHessian</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>helloAlso</servlet-name>
<servlet-class>com.hit.hessian.service.BasicServiceAlso</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>helloAlso</servlet-name>
<url-pattern>/helloHessianAlso</url-pattern>
</servlet-mapping>
</web-app>
在服務端端,我們定義了兩個 Service,分別為 BasicService 和 BasicServiceAlso,不同之處在於是否繼承 HessianServlet,BasicService 是一個 POJO(簡單的 Java 類),而 BasicServiceAlso 則是一個 Servlet. 在web.xml
裡,BasicService 是通過home-class
和home-api
兩個引數傳遞給 HessianServlet,然後再將 HessianServlet 配置到web.xml
的 Servlet 裡來實現服務配置到容器的。而 BasicServiceAlso ,則是直接將自己(它自己就是個 Servlet)配置到web.xml
的 Servlet 來實現配置到容器中的。如果我們在一個應用中要實現多個 Hessian 服務,應該採用這種方式。
建立客戶端:
/**
* 建立與伺服器端相同的介面
*/
package com.hit.hessian.service;
public interface Basic {
public String sayHello();
}
/*
* 建立測試客戶端
*/
package com.hit.hessian.client;
import java.net.MalformedURLException;
import com.caucho.hessian.client.HessianProxyFactory;
import com.hit.hessian.service.Basic;
public class HessianClient {
public static void main(String[] args) throws MalformedURLException {
//String url = "http://localhost:8080/HessianWeb/helloHessian";
String url = "http://localhost:8080/HessianWeb/helloHessianAlso";
HessianProxyFactory factory = new HessianProxyFactory();
Basic basic = (Basic)factory.create(Basic.class, url);
System.out.println(basic.sayHello());
}
}
客戶端要定義一個同伺服器端一模一樣的介面,然後通過 HessianProxyFactory 獲得代理,並呼叫遠端服務的方法。注意:這裡筆者故意將客戶端與伺服器端的 Basic 介面的包定義成不同的路徑(一個是 com.hit.hessian.service,一個是 com.hit2.hessian.service),經過驗證這樣是可以的,但是推薦最好兩者一模一樣。
4.3 常見的異常及錯誤的解決方案
在執行以上的程式碼時,一不小心就會報錯,常見的異常及錯誤的解決方法有:
(1)在啟動 tomcat 的時候,出現如下問題java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet
,而在工程中是可以找到相應的 jar 檔案,怎麼辦?
解決方法:可能是在工程的WEB-INF/lib
下面沒有載入相應的 jar 檔案 。
(2)控制檯顯示org.springframework.remoting.RemoteAccessException: Cannot access Hessian service at XXX
這個異常。
解決方法:出現這個異常一般是因為服務端操作出現異常引起的,需要重新檢查程式碼。
(3)編譯時錯誤無法訪問 javax.servlet.http.HttpServlet ; 未找到 javax.servlet.http.HttpServlet 的類檔案。
(4)在 WEB-INF 目錄下載入完 Hessian 的 jar 包後,程式碼仍然報錯,怎麼辦?
解決方法:出現這個情況的時候,我們可以嘗試著把 Hessian 的 jar 包再載入到External Libraries
裡面。
(5)在通過 Hessian 協議進行遠端服務呼叫的過程中,程式碼總是報錯,異常資訊提示為HessianConnectionException
,如何解決?
解決方法:出現這種異常的原因有可能是介面的實現類沒有進行注入,即通過@component("介面名稱,第一個字母小寫")
註解進行注入,也有可能是程式碼沒有提交到伺服器,從而導致程式碼沒有生效。
參考資料: