基于 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 的类文件。

  解决方法:可能是环境变量没有配置,或者就是根本没有包含该类的jar包,可以参考 「出现 javax.servlet.http.HttpServlet 错误的原因及解决方法」进行解决。

(4)在 WEB-INF 目录下加载完 Hessian 的 jar 包后,代码仍然报错,怎么办?

  解决方法:出现这个情况的时候,我们可以尝试着把 Hessian 的 jar 包再加载到External Libraries里面。

(5)在通过 Hessian 协议进行远程服务调用的过程中,代码总是报错,异常信息提示为HessianConnectionException,如何解决?

  解决方法:出现这种异常的原因有可能是接口的实现类没有进行注入,即通过@component("接口名称,第一个字母小写")注解进行注入,也有可能是代码没有提交到服务器,从而导致代码没有生效。


参考资料:

[1] Hessian - 百度百科
[2] Hessian 轻量级远程调用方案
[3] Hessian 远程接口调用原理
[4] Hessian 轻量级二进制远程调用框架
[5] Hessian 学习
[6] Hessian 简单实用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安正勋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值