- 背景
最近在处理一个hessian的反序列化问题时,因为服务端使用了pojo bean中多了一个enum属性,导致客户端在反序列化时疯狂的在打印日志。警告说找不到对应的enum class,因为项目中本身是设置了log4j的根输出为一个文件。
比较奇怪的是,hessian对应的日志输出全都打印到了控制台(虽然我们对console进行了重定向输出),导致对应的文件达到几百MB。无奈之下,仔细分析了下hessian的源码。
介绍
先看一张hessian主要的几个概念图
说明:
- Serializer 序列化的接口
- Deserializer 反序列化的接口
- AbstractHessianInput hessian自定义的输入流,提供对应的read各种类型的方法
- AbstractHessianOutput hessian自定义的输出流,提供对应的write各种类型的方法
AbstractSerializerFactory介绍
serializerFactory从字面意思上也看的出来,是管理和维护对应序列化/反序列化机制的工厂。默认的几种实现
- SerializerFactory 标准的实现
- ExtSerializerFactory 我们可以设置自定义的序列化机制,通过该Factory可以进行扩展。
- BeanSerializerFactory 对SerializerFactory的默认object的序列化机制进行强制指定,指定为BeanSerializer。 具体BeanSerializer的实现后面再表。
自定义序列化机制的扩展:
- 实现Serializer/Deserializer接口
-
ExtSerializerFactory extSerializerFactory = new ExtSerializerFactory(); extSerializerFactory.addSerializer(class , mySerializer); //添加自定义的序列化接口 extSerializerFactory.addDeserializer(class , myDeserializer); //添加自定义的反序列化接口 serializerFactory.addFactory(extSerializerFactory); //注册ext到序列化工厂
SerializerFactory介绍:
先看一下hesian提供的Serializer/Derializer几种默认实现
具体的实现就不细说,有兴趣的自己看代码去。
下面提一下我在看得过程中比较在意和疑惑过的点。
1. Object对象的序列化/反序列化
答:JavaSerializer或者BeanSerializer。这两者的区别
- JavaSerializer是通过反射获取所有bean的属性进行序列化,排除static和transient属性,对其他所有的属性进行递归序列化处理(比如属性本身是个对象)。
- BeanSerializer是遵循pojo bean的约定,扫描bean的所有方法,发现存在get和set方法的属性进行序列化,它并不直接直接操作所有的属性,比较温柔。 注意:BeanSerializer将会无法处理你的boolean属性,因为通过默认的eclipse生成的方法是以isXXX打头,不会被序列化。
2. 枚举对象的序列化/反序列化
答:看过类图后,就很明显的发现存在一个EnumSerializer和EnumDeserializer实现。大家都知道枚举对象全都继承于enum对象,所以EnumSerializer会反射调用name方法,EnumDeserializer是反射调用valueof方法。这样就很明显了,如果服务端多了一个枚举值定义,
客户端反序列化会出现异常,不会是一个兼容的过程。
补充:测试过程中,hessian 3.1.3版本存在enum序列化问题,低级的问题。
3. 服务端抛异常后的序列化/反序列化
答:这个也是在做rpc调用,一个比较容易被遗忘的点,只关注了正常的业务功能的流程,却没有考虑对应的异常处理的序列化和反序列化。hessian提供了ThrowableSerializer和StackTraceElementDeserializer进行序列化处理。
- ThrowableSerializer会按照object的序列化方式,传递对应的信息到客户端。包括stackTrace和detailMessage等。
- StackTraceElementDeserializer反序列化对应的异常栈信息。异常的其他属性通过JavaSerializer进行反序列化处理。
补充:测试过程中,hessian 3.0.20,3.1.3版本存在问题,异常的反序列化在hessian 1.0协议是正常的,到2.0就会出现错误。 具体的问题也有人报告了bug :
http://maillist.caucho.com/pipermail/hessian-interest/2008-February/000297.html ,升级到3.1.5之后就解决了.
测试代码
public static void exceptionTest() throws Exception {
Exception exception = null;
try {
FileInputStream stream = new FileInputStream("notfound");
} catch (FileNotFoundException e) {
exception = e;
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
HessianOutput out = new HessianOutput(bos);
out.writeObject(exception);
out.flush();
byte[] bytes = bos.toByteArray();
ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
HessianInput in = new HessianInput(bin);
Exception read = (Exception) in.readObject(FileNotFoundException.class);
read.printStackTrace();
System.out.println(read);
}
hessian支持对流进行压缩处理,可以看下 Deflation
Hessian2Output out = new Hessian2Output(bos);
out = envelope.wrap(out); //包装为压缩
Hessian2Input in = new Hessian2Input(bin);
in = envelope.unwrap(in); //解缩
压缩慎用,在测试过程中有一些磕磕碰碰的小问题。
后记
hessian序列化机制的性能比较,后续补上。 主要是和原先的几种序列化协议相关数据对比
序列化数据对比
bytes字节数对比
具体的数字:
protobuf | jackson | xstream | Serializable | hessian2 | hessian2压缩 | hessian1 | |
序列化(单位ns) | 1154 | 5421 | 92406 | 10189 | 26794 | 100766 | 29027 |
反序列化(单位ns) | 1334 | 8743 | 117329 | 64027 | 37871 | 188432 | 37596 |
bytes | 97 | 311 | 664 | 824 | 374 | 283 | 495 |