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 包可以通过下面提供的两个地址下载:
- 方法1 -> hessian-4.0.37(最新版本的jar包)
- 方法2 -> hessian官方网站
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 的类文件。
解决方法:可能是环境变量没有配置,或者就是根本没有包含该类的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 简单实用