hessian基础

介绍

Hessian是caucho公司的工程项目,为了达到或超过 ORMI/Java JNI 等其他跨语言/平台调用的能力设计而出,在 2004 点发布 1.0 规范,一般称之为 Hessian ,并逐步迭代,在 Hassian jar 3.2.0 之后,采用了新的 2.0 版本的协议,一般称之为 Hessian 2.0。
Hessian是基于二进制的实现,传输数据更小更快,基于HTTP协议传输

RPC

Remote Procedure Call Protocol,远程过程调用。早在学RMI时就遇到了这个词。RPC它以标准的二进制格式来定义请求的信息(请求对象、方法、参数等),这种方法传输信息的优点之一就是跨语言及操作系统。
*在面向对象编程范式下,RMI其实就是RPC的一种具体实现
PRC协议的一次远程通信过程:

  1. 客户端发起请求,并且按照RPC协议格式填充信息
  2. 填充完毕后将二进制格式文件转化为流,通过传输协议进行传输
  3. 服务端接收到流后,将其转换为二进制格式文件,并且按照RPC协议格式获取请求的信息并进行处理
  4. 处理完毕后将结果按照RPC协议格式写入二进制格式文件中并返回

基本使用

依赖

1
2
3
4
5
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.63</version>
</dependency>

因为 Hessian 基于 HTTP 协议,所以通常通过 Web 应用来提供服务
基于Servlet项目
通过把提供服务的类注册成 Servlet 的方式来作为 Server 端进行交互。

  1. 首先创建一个可提供服务的API接口

    1
    2
    3
    4
    5
    6
    package com.example.hessian;

    public interface Greeting {
    String sayHello();
    }

    服务端需要有一个该方法的具体实现,这里通过使该类继承自com.caucho.hessian.server.HessianServlet来将其标记为一个提供服务的 Servlet

  2. 创建服务端,通过把提供服务的类注册成 Servlet 的方式来作为 Server 端进行交互 .

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.example.hessian;

    import com.caucho.hessian.server.HessianServlet;

    public class Server extends HessianServlet implements Greeting {
    @Override
    public String sayHello(){
    return "baicany";
    }
    }

    3.配置web.xml,为服务端设置映射

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <?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_4_0.xsd"
    version="4.0">

    <servlet>
    <servlet-name>hesian</servlet-name>
    <servlet-class>com.example.hessian.Server</servlet-class>
    </servlet>

    <servlet-mapping>
    <servlet-name>hesian</servlet-name>
    <url-pattern>/hessian</url-pattern>
    </servlet-mapping>

    </web-app>

    servlet-name要设置相同,url-pattern和servlet-class为映射关系
    表示访问/hessian,触发相关Servlet后端类
    4.将服务端部署上tomcat
    项目结构是
    image.png

  3. 客户端通过com.caucho.hessian.client.HessianProxyFactory工厂类创建对接口的代理对象,并进行调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package com.example.hessian;

    import com.caucho.hessian.client.HessianProxyFactory;

    public class HessianClient {
    public static void main(String[] args) throws Exception {
    String url = "http://localhost:8081/hessian";

    HessianProxyFactory hessianProxyFactory = new HessianProxyFactory();
    Greeting hello = (Greeting) hessianProxyFactory.create(Greeting.class, url);

    System.out.println(hello.sayHello());//baicany
    }
    }

远程调用源码分析

HessianProxyFactory的实例化,即创建代理工厂
HessianProxyFactory hessianProxyFactory = new HessianProxyFactory();
image.png主要是初始化类加载器和相应resolver对象,resolver对象中则是设置好代理工厂
接着通过HessianProxyFactory#create方法获取接口代理类对象

1
Greeting hello = (Greeting) hessianProxyFactory.create(Greeting.class, url); 

首先对url进行URL对象的封装,用实例化时的类加载器进行代理对象的加载。用动态代理的方式去返回我们的代理对象

1
2
3
4
5
6
7
8
9
public Object create(Class<?> api, URL url, ClassLoader loader) {
if (api == null) {
throw new NullPointerException("api must not be null for HessianProxyFactory.create()");
} else {
InvocationHandler handler = null;
handler = new HessianProxy(url, this, api);
return Proxy.newProxyInstance(loader, new Class[]{api, HessianRemoteObject.class}, handler);
}
}

所以看看 HessianProxy#invoke方法

1
2
3
4
5
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String mangleName;
synchronized(this._mangleMap) {
mangleName = (String)this._mangleMap.get(method);
}

先从获取mangleMap里面获取我们要的方法如果没有就会跟进下面代码去调用对应的方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
if (methodName.equals("equals") && params.length == 1 && params[0].equals(Object.class)) {
Object value = args[0];
if (value != null && Proxy.isProxyClass(value.getClass())) {
Object proxyHandler = Proxy.getInvocationHandler(value);
if (!(proxyHandler instanceof HessianProxy)) {
return Boolean.FALSE;
}

HessianProxy handler = (HessianProxy)proxyHandler;
return new Boolean(this._url.equals(handler.getURL()));
}

return Boolean.FALSE;
}

if (methodName.equals("hashCode") && params.length == 0) {
return new Integer(this._url.hashCode());
}

if (methodName.equals("getHessianType")) {
return proxy.getClass().getInterfaces()[0].getName();
}

if (methodName.equals("getHessianURL")) {
return this._url.toString();
}

if (methodName.equals("toString") && params.length == 0) {
return "HessianProxy[" + this._url + "]";
}

if (!this._factory.isOverloadEnabled()) {
mangleName = method.getName();
} else {
mangleName = this.mangleName(method);
}

isOverloadEnabled默认为false,那就用mangleName方法去设置mangleName.
然后将获取的方法存进manglemap
然后调用sendRequest方法

1
2
3
4
5
6
7
8
protected HessianConnection sendRequest(String methodName, Object[] args) throws IOException {
HessianConnection conn = null;
conn = this._factory.getConnectionFactory().open(this._url);
boolean isValid = false;

HessianConnection var14;
try {
this.addRequestHeaders(conn);

先连接了url,然后加了请求头

1
2
3
4
5
6
7
8
9
protected void addRequestHeaders(HessianConnection conn) {
conn.addHeader("Content-Type", "x-application/hessian");
conn.addHeader("Accept-Encoding", "deflate");
String basicAuth = this._factory.getBasicAuth();
if (basicAuth != null) {
conn.addHeader("Authorization", basicAuth);
}

}

然后通过HessianURLConnection#getOutputstream去获取我们序列化流

1
2
3
4
OutputStream os = null;

try {
os = conn.getOutputStream();

这里是向流中写入相关数据,call方法是将方法调用信息写入进流。

1
2
3
AbstractHessianOutput out = this._factory.getHessianOutput((OutputStream)os);
out.call(methodName, args);
out.flush();

然后再调用HessianURLConnection#sendRequest

1
conn.sendRequest()

将流的信息发出去,再从流信息获取要调用的方法,返回执行方法结果
最后返回这个HessianURLConnection对象
返回后,通过这个对象获取Input流

1
is = this.getInputStream(conn);

最后通过这个流获取方法调用结果

1
2
3
4
5
6
7
8
value = in.readReply(method.getReturnType());
if (value instanceof InputStream) {
value = new ResultInputStream(conn, (InputStream)is, in, (InputStream)value);
is = null;
conn = null;
}

var12 = value;

发现是tag是82和70的话都会调用this.object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Object readReply(Class expectedClass) throws Throwable {
int tag = this.read();
if (tag == 82) {
return this.readObject(expectedClass);
} else if (tag == 70) {
HashMap map = (HashMap)this.readObject(HashMap.class);
throw this.prepareFault(map);
} else {
StringBuilder sb = new StringBuilder();
sb.append((char)tag);

int ch;
try {
while((ch = this.read()) >= 0) {
sb.append((char)ch);
}
} catch (IOException var5) {
log.log(Level.FINE, var5.toString(), var5);
}

throw this.error("expected hessian reply at " + this.codeName(tag) + "\n" + sb);
}
}

发现会用一个反序列化工厂类去获取结果

1
2
Object value = this.findSerializerFactory().getDeserializer(cl).readObject(this);
return value;

总结:先将要调用的方法和参数等信息写进Output流,发送HTTP,服务端通过流信息将方法调用结果写入Input流,最后返回给客户端

服务端处理输出流

com.caucho.hessian.server.HessianServlet类是javax.servlet.http.HttpServlet的一个子类,所以这个类的init方法将会承担一些初始化的功能,而service方法将会是相关处理的起始位置。
init方法:主要涉及_homeImpl(继承HessianServlet的实现类)、_homeAPI(继承HessianServlet的Clas)、_homeSkeleton(HessianSkeleton对象,其父类会封装实现类的方法)变量的初始化
service方法:HessianServlet只支持POST请求,若是POST请求会进行如下操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void service(ServletRequest request, ServletResponse response) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;
if (!req.getMethod().equals("POST")) {
res.setStatus(500);
PrintWriter out = res.getWriter();
res.setContentType("text/html");
out.println("<h1>Hessian Requires POST</h1>");
} else {
String serviceId = req.getPathInfo();
String objectId = req.getParameter("id");
if (objectId == null) {
objectId = req.getParameter("ejbid");
}

ServiceContext.begin(req, res, serviceId, objectId);

try {
InputStream is = request.getInputStream();
OutputStream os = response.getOutputStream();
response.setContentType("x-application/hessian");
SerializerFactory serializerFactory = this.getSerializerFactory();
this.invoke(is, os, objectId, serializerFactory);
} catch (RuntimeException var15) {
throw var15;
} catch (ServletException var16) {
throw var16;
} catch (Throwable var17) {
throw new ServletException(var17);
} finally {
ServiceContext.end();
}

}
}

进入invoke方法看处理逻辑

1
2
3
4
5
6
7
8
protected void invoke(InputStream is, OutputStream os, String objectId, SerializerFactory serializerFactory) throws Exception {
if (objectId != null) {
this._objectSkeleton.invoke(is, os, serializerFactory);
} else {
this._homeSkeleton.invoke(is, os, serializerFactory);
}

}

没有指定的objectid就会用_homeSkeleton的invoke方法
HessianSkeleton 是 AbstractSkeleton 的子类,用来对 Hessian 提供的服务进行封装。这里想起来rmi的skelection
首先 AbstractSkeleton 初始化时接收调用接口的类型,并按照自己的逻辑把接口中的方法保存在 _methodMap 中,包括“方法名”、“方法名__方法参数个数”、“方法名_参数类型_参数2类型”等自定义格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected AbstractSkeleton(Class apiClass) {
this._apiClass = apiClass;
Method[] methodList = apiClass.getMethods();

for(int i = 0; i < methodList.length; ++i) {
Method method = methodList[i];
if (this._methodMap.get(method.getName()) == null) {
this._methodMap.put(method.getName(), methodList[i]);
}

Class[] param = method.getParameterTypes();
String mangledName = method.getName() + "__" + param.length;
this._methodMap.put(mangledName, methodList[i]);
this._methodMap.put(mangleName(method, false), methodList[i]);
}

}

HessianSkeleton 初始化时将实现类保存在成员变量 _service 中

1
2
3
4
5
6
7
8
9
10
11
public HessianSkeleton(Object service, Class<?> apiClass) {
super(apiClass);
if (service == null) {
service = this;
}

this._service = service;
if (!apiClass.isAssignableFrom(service.getClass())) {
throw new IllegalArgumentException("Service " + service + " must be an instance of " + apiClass.getName());
}
}

接着看invoke方法

1
HessianInputFactory.HeaderType header = this._inputFactory.readHeader((InputStream)is);

去读取协议头种类根据种类去选择是那种数据交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    case CALL_1_REPLY_1:
in = this._hessianFactory.createHessianInput((InputStream)is);
out = this._hessianFactory.createHessianOutput((OutputStream)os);
break;
case CALL_1_REPLY_2:
in = this._hessianFactory.createHessianInput((InputStream)is);
out = this._hessianFactory.createHessian2Output((OutputStream)os);
break;
case HESSIAN_2:
in = this._hessianFactory.createHessian2Input((InputStream)is);
((AbstractHessianInput)in).readCall();
out = this._hessianFactory.createHessian2Output((OutputStream)os);
break;
default:
throw new IllegalStateException(header + " is an unknown Hessian call");
}

主要是调用方法的查找和参数的反序列化,反序列化后进行反射调用

1
2
3
4
5
6
7
8
9
10
Object[] values = new Object[args.length];

for(int i = 0; i < args.length; ++i) {
values[i] = in.readObject(args[i]);
}

Object result = null;

try {
result = method.invoke(service, values);

最后写入流中返回

1
2
3
in.completeCall();
out.writeReply(result);
out.close();

序列化与反序列化流程

Hessian 的序列化反序列化流程有几个关键类,一般包括输入输出流、序列化/反序列化器、相关工厂类等等.
首先是输入和输出流,Hessian 定义了 AbstractHessianInput/AbstractHessianOutput 两个抽象类,用来提供序列化数据的读取和写入功能。Hessian/Hessian2/Burlap 都有这两个类各自的实现类来实现具体的逻辑。
序列化中,对于输出流关键类为 AbstractHessianOutput 的相关子类,这些类都提供了 call 等相关方法执行方法调用,writeXX 方法进行序列化数据的写入,这里以 Hessian2Output 为例。
除了基础数据类型,主要关注的是对 Object 类型数据的写入方法 writeObject:
发现会根据指定类型去获取类的序列化器
image.png
在当前版本中,可看到一共有 29 个子类针对各种类型的数据。对于自定义类型,就会用JavaSerializer/UnsafeSerializer/JavaUnsharedSerializer 进行相关的序列化动作,默认情况下是 UnsafeSerializer。
image.png
跟进里面的 loadSerializer 的方法没找到我们想要的类的话就会用getDefaultSerializer获取默认的

1
2
3
4
5
else if (JavaSerializer.getWriteReplace(cl) != null) {
Serializer baseSerializer = getDefaultSerializer(cl);

return new WriteReplaceSerializer(cl, getClassLoader(), baseSerializer);
}

getDefaultSerializer方法里面就会返回一个UnsafeSerializer
UnsafeSerializer#writeObject 方法兼容了 Hessian/Hessian2 两种协议的数据结构,会调用 writeObjectBegin 方法开始写入数据
image.png
writeObjectBegin 这个方法是 AbstractHessianOutput 的方法,Hessian2Output 重写了这个方法,而其他实现类没有。也就是说在 Hessian 1.0 和 Burlap 中,写入自定义数据类型(Object)时,都会调用 writeMapBegin 方法将其标记为 Map 类型。在 Hessian 2.0 中,将会调用 writeDefinition20 和 Hessian2Output#writeObjectBegin 方法写入自定义数据,就不再将其标记为 Map 类型。
image.png
再看反序列化,对于输入流关键类为 AbstractHessianInput 的子类,这些类中的 readObject 方法定义了反序列化的关键逻辑。基本都是长达 200 行以上的 switch case 语句。在读取标识位后根据不同的数据类型调用相关的处理逻辑。这里还是以 Hessian2Input 为例。
image.png
与序列化过程设计类似,Hessian 定义了 Deserializer 接口,并为不同的类型创建了不同的实现类。这里重点看下对自定义类型对象的读取。
在 Hessian 1.0 的 HessianInput 中,没有针对 Object 的读取,而是都将其作为 Map 读取,在序列化的过程中我们也提到,在写入自定义类型时会将其标记为 Map 类型。
image.png
MapDeserializer#readMap 方法提供了针对 Map 类型数据的处理逻辑。跟据map类型实例化不同的map
image.png
然后读取内容,将键值put到map中
image.png
在 Hessian 2.0 中,则是提供了 UnsafeDeserializer 来对自定义类型数据进行反序列化,关键方法在 readObject 处。
通过instantiate方法通过unsafe实例化类,然后再readObject方法中反序列化 读取filed并写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Object readObject(AbstractHessianInput in,
Object []fields)
throws IOException
{
try {
Object obj = instantiate();

return readObject(in, obj, (FieldDeserializer2 []) fields);
} catch (IOException e) {
throw e;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IOExceptionWrapper(_type.getName() + ":" + e.getMessage(), e);
}
}

Serializable

在 Java 原生反序列化中,实现了 java.io.Serializable 接口的类才可以反序列化。Hessian 象征性的支持了这种规范,具体的逻辑如下图,在获取默认序列化器时,判断了类是否实现了 Serializable 接口。

1
2
3
4
5
6
7
8
9
protected Serializer getDefaultSerializer(Class cl) {
if (this._defaultSerializer != null) {
return this._defaultSerializer;
} else if (!Serializable.class.isAssignableFrom(cl) && !this._isAllowNonSerializable) {
throw new IllegalStateException("Serialized class " + cl.getName() + " must implement java.io.Serializable");
} else {
return (Serializer)(this._isEnableUnsafeSerializer && JavaSerializer.getWriteReplace(cl) == null ? UnsafeSerializer.create(cl) : JavaSerializer.create(cl));
}
}

还有一些可以看看su18师傅的

参考

https://nivi4.notion.site/
https://su18.org/post/hessian/#%E4%B8%80%E4%BA%9B%E7%BB%86%E8%8A%82
https://goodapple.top/archives/1193