1. 程式人生 > >基於 Hessian 輕量級遠端呼叫的原理及示例

基於 Hessian 輕量級遠端呼叫的原理及示例

1 簡介

  Hessian 是 Caucho 公司開發的一種基於二進位制 RPC 協議(Remote Procedure Call protocol)的輕量級遠端呼叫框架,其使用簡單的方法提供了 RMI 的功能。 相比 WebService,Hessian 更簡單、快捷,它主要包括 Hessian 遠端呼叫協議、Hessian 序列化協議和客戶端服務端代理。特別提示,Hessian 遠端呼叫框架是構建在 HTTP 協議之上的。

2 呼叫過程

hessian呼叫過程圖示

其中,步驟 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-classhome-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("介面名稱,第一個字母小寫")註解進行注入,也有可能是程式碼沒有提交到伺服器,從而導致程式碼沒有生效。

參考資料: