Hessian 序列化代码分析及业务场景学习

介绍

Hessian是一个轻量级的remotingonhttp工具,使用简单的方法提供了RMI的功能。早在08年有人做过测试:一个UserData类,有一个字符串属性,一个日期属性,一个double属性,分别用java,hessian来序列化一百万次,结果让人吃惊,不止是hessian序列化的速度要比java的快上一倍,而且hessian序列化后的字节数也要比java的少一倍。

序列化

序列化宏观来看就四步

  1. 建立进行序列化的工厂类
  2. 用工厂类找到要要进行序列化类对应的序列化器
  3. 序列化器对将进行序列化的对象进行内省
  4. 内省完成后调用序列化器的writeObject(writeObject中按照一定规则把数据写入byte流)

下面用Hessian简单序列化一个对象来看

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
import com.caucho.hessian.io.HessianOutput;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;

class User implements Serializable {
String name;
int age;

User() {
age = 18;
name = "cl0und";
}
}


public class HessianTest {
public static void main(String[] args) throws IOException {
User user = new User();
ByteArrayOutputStream os=new ByteArrayOutputStream();
HessianOutput output=new HessianOutput(os);
output.writeObject(user);
}
}

建立进行序列化的工厂类

把ByteArrayOutputStream放入了HessianOutput中典型的装饰器模式。
image.png

新建序列工厂类(后续会通过工厂类来寻找类的对应序列化器)。
image.png

工厂类的创建过程中,在类静态块中加入了java各种类型的反序列器以及和Hessian有关的反序列器。这里和序列化没关系,只是提一嘴。image.png

在类初始化完成后,正式进入SerializerFactory的构造方法。可以看到SerializerFactory允许不安全的序列化,设置默认的上下文工厂类是_contextFactory,这个_contextFactory是生产序列化器的工厂。
image.png

image.png

用工厂类找到要要进行序列化类对应的序列化器

根据传入类型获取相应的序列化器。
image.png

获取序列化器的规则是如果_cachedSerializerMap里面又就直接从里面取,如果没有就用loadSerializer找,找出来的加入_cachedSerializerMap避免二次加载。
image.png

loadSerializer找的逻辑是先看有没有其它工厂类,如果有就用,如果没有就用_contextFactory造。
image.png

因为User是自建类型_contextFactory造不出对应序列化器。
image.png

下一步尝试getCustomSerializer来找序列化器,看方法的意思应该是找是否有客户自己定制的序列化器。生成序列器规则是:如果_customSerializerMap有就直接返回,如果没有就按照_类名+HessianSerializer_的规则来找。很遗憾我们并没有对User做个性化定制。
image.png

因为没有我们没有对User做个性化的序列化器,所以最后会调用getDefaultSerializer来返回默认序列化器。又因为User既不是内置类也不是和hessian有关的特殊类但是允许了不安全的序列化,所以最后被选中的序列化器是UnsafeSerializer。
image.png

序列化器对序列化对象进行内省

在构造方法中使用introspect进行内省。
image.png

image.png

introspect最后几步是在为类里面的属性找序列化器(getFiledSerializer)。
image.png

比如这里User类中name属性是String,就创建针对String的序列化器。
image.png

String的序列化器包含了field类及其偏移量。
image.png

创建类及及其字段序列化器后一路向外传,然后调用writeObject。
image.png

image.png

writeObjectBegin 写了Object的魔术头、长度、类名
image.png
image.png
writeObject10 负责序列化字段
image.png

先是写要序列化的字符串魔术值、字段名长度、字段名,其中 os.write(83) 代表 S 代表字符串。image.png

String类型学序列化的时候
image.png
image.png

int类型序列化的时候
image.png
image.png

最后的结果
image.png

业务场景

环境搭建

说穿了就是RPC,用实际的代码来看看
pom.xml加入

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.60</version>
</dependency>
</dependencies>

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>HessianServlet</servlet-name>
<servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>
<init-param>
<param-name>service-class</param-name>
<param-value>org.syclover.hessian.BasicService</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>HessianServlet</servlet-name>
<url-pattern>/api/service</url-pattern>
</servlet-mapping>
</web-app>

代码忘记从网上哪里copy的了,侵删。

client/server端共有代码

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

public interface IBasicApi {

/* 设置用户名 */
public boolean setUserName(String name);

/* 获取问候 */
public String sayHello();

/* 获取用户信息 */
public User getUser();

}

client端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.syclover.hessian;


import com.caucho.hessian.client.HessianProxyFactory;
import java.net.MalformedURLException;

public class ClientTest {

public static void main(String[] args) throws MalformedURLException {

String url = "http://localhost:8080/hessianTest_war_exploded/api/service";
HessianProxyFactory factory = new HessianProxyFactory();
IBasicApi api = (IBasicApi) factory.create(IBasicApi.class, url);
api.setUserName("mahc");
System.out.println(api.sayHello());
System.out.println(api.getUser().getName());
System.out.println(api.getUser().getAge());
}
}

server端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.syclover.hessian;

import org.syclover.hessian.User;

public class BasicService implements IBasicApi {

private String name;

@Override
public boolean setUserName(String name) {
this.name = name;
return false;
}

@Override
public String sayHello() {
return "Hello " + name + ",Welcome to Hessian!";
}

@Override
public User getUser() {
return new User(name, 23);
}
}
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
package org.syclover.hessian;


import java.io.Serializable;

public class User implements Serializable {

private String name;
private Integer age;

public User() {
super();
}

public User(String name, Integer age) {
super();
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}
}

这里为了测试方便把c/s放到一起了
image.png

进行RPC

image.png

参考

Hessian使用教程
JVM 类装载原理分析-ClassLoader原理分析
hessian 序列化实现 初探

Author

李三(cl0und)

Posted on

2020-01-03

Updated on

2020-07-11

Licensed under