Hessian 协议解释与实战(五):对象与映射

前段时间,翻译了 Hessian 2.0 的序列化协议,发布在了 Hessian 2.0 序列化协议(中文版)。但是,其中有很多言语不详之处。所以,接下来会用几篇文章来详细解释并实践一下 Hessian 序列化协议,以求做到知其然知其所以然。目录如下:
Hessian 协议解释与实战(一):布尔、日期、浮点数与整数 — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。
Hessian 协议解释与实战(二):长整型、二进制数据与 Null — 介绍长整数类型数据、二进制数据和
null
等三种类型的数据的处理。Hessian 协议解释与实战(三):字符串 — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多,处理细节也有繁琐,所以单独成篇来介绍。
Hessian 源码分析(Java) — 开始第四篇分析之前,先来介绍一下 Hessian 的源码实现。方便后续展开说明。
Hessian 协议解释与实战(四):数组与集合 — 铺垫了一些关于实例对象的处理,重点介绍关于数组和集合的相关处理。
Hessian 协议解释与实战(五):对象与映射 — 重点介绍关于对象与映射的相关处理。
未完待续,敬请继续关注 "地瓜哥"博客网。
在上一篇文章 Hessian 协议解释与实战(四):数组与集合 中研究了数组和集合的处理方式。接下来介绍对象和映射的处理。
基础工具方法
基础工具方法就不再赘述,请直接参考 Hessian 协议解释与实战(一):基础工具方法 中提到的几个方法。
另外,在 Hessian 协议解释与实战(四):数组与集合 中,又对一些方法做了扩展和更新。
下面,我们来看一看映射的处理。
映射
坦白讲,个人觉得 Hessian 2.0 序列化协议(中文版):映射 的协议定义写的非常模糊。倒是给的示例,还可圈可点。
相关代码实现
通过 Hessian 源码分析(Java) 可以指定, MapSerializer
是处理映射的 Serializer
。所以,只需要关注一下 MapSerializer
就可以对映射的处理一目了然。结合代码,可以看出,映射的序列化流程大致如下:
AbstractHessianOutput.writeMapBegin(String type)
— 写入映射起始信息;遍历
Map.Entry
,并且是先序列化 Key,然后序列化 Value;AbstractHessianOutput.writeMapEnd()
— 写入映射结束信息。
另外,在 Hessian 源码分析(Java) 中,也提到在 Hessian2Output
中实现了 AbstractHessianOutput
的接口。所以,只需要关注 Hessian2Output
对上述方法的实现即可。
在 Hessian2Output
中可以看出:
Hessian2Output.writeMapBegin(String type)
有两个类型:需要写入类型:
M
,前置标志位后是映射类型信息;不需要写入类型:
H
,这里没有写入类型信息。
AbstractHessianOutput.writeMapEnd()
— 映射结束信息只有一个结束标志位:Z
。这个标志位,在 Hessian 协议解释与实战(四):数组与集合:Collection<Integer>.iterator
👉IteratorSerializer
中也用到了。
下面开始做实验来验证。
HashMap
接下来,我们看一下序列化操作:
/**
* 测试 HashMap 的序列化
*
* @author D瓜哥 · https://www.diguage.com/
*/
@Test
public void testHashMap() throws Throwable {
// 由 MapSerializer 来处理。分三步来处理:
// 1、首先,写入前置标志位 BC_MAP_UNTYPED = 'H'
// 2、其次,遍历 Map.Entry,并将其序列化:①Key ②Value
// 3、最后,写入结束标志位 BC_END = 'Z'
Map<Integer, Car> map = new HashMap<>();
map.put(1, new Car("diguage", 47));
objectTo(map);
}
// -- 输出结果 ------------------------------------------------
== Object: java.util.HashMap ==
== Key Object: java.lang.Integer ==
== Val Object: com.diguage.Car ==
{"1":{"name":"diguage","age":47}}
== byte array: hessian result ==
.... 0 ~ 40 ....
72 0x48 01001000 H
-111 0x91 10010001
67 0x43 01000011 C
15 0x0F 00001111
99 0x63 01100011 c
111 0x6F 01101111 o
109 0x6D 01101101 m
46 0x2E 00101110 .
100 0x64 01100100 d
105 0x69 01101001 i
103 0x67 01100111 g
117 0x75 01110101 u
97 0x61 01100001 a
103 0x67 01100111 g
101 0x65 01100101 e
46 0x2E 00101110 .
67 0x43 01000011 C
97 0x61 01100001 a
114 0x72 01110010 r
-110 0x92 10010010
4 0x04 00000100
110 0x6E 01101110 n
97 0x61 01100001 a
109 0x6D 01101101 m
101 0x65 01100101 e
3 0x03 00000011
97 0x61 01100001 a
103 0x67 01100111 g
101 0x65 01100101 e
96 0x60 01100000 `
7 0x07 00000111
100 0x64 01100100 d
105 0x69 01101001 i
103 0x67 01100111 g
117 0x75 01110101 u
97 0x61 01100001 a
103 0x67 01100111 g
101 0x65 01100101 e
-65 0xBF 10111111
90 0x5A 01011010 Z
从结果上来看,跟我们上面的分析差不多,确定了一些细节:
首先,写入前置标志位
0x48
(H
)其次,遍历
Map.Entry
,并将其序列化Key
Value
最后,写入结束标志位
0x5A
(Z
)。
接下来再看看其他类型的 Map
的处理情况。
TreeMap
来看看 TreeMap
的处理情况:
/**
* 测试 TreeMap 的序列化
*
* @author D瓜哥 · https://www.diguage.com/
*/
@Test
public void testTreeMap() throws Throwable {
Car c = new Car("diguage", 47);
Map<Integer, Car> map = new TreeMap<>();
map.put(1, c);
objectTo(map);
}
// -- 输出结果 ------------------------------------------------
== Object: java.util.TreeMap ==
== Key Object: java.lang.Integer ==
== Val Object: com.diguage.Car ==
{"1":{"name":"diguage","age":47}}
== byte array: hessian result ==
.... 0 ~ 58 ....
77 0x4D 01001101 M
17 0x11 00010001
106 0x6A 01101010 j
97 0x61 01100001 a
118 0x76 01110110 v
97 0x61 01100001 a
46 0x2E 00101110 .
117 0x75 01110101 u
116 0x74 01110100 t
105 0x69 01101001 i
108 0x6C 01101100 l
46 0x2E 00101110 .
84 0x54 01010100 T
114 0x72 01110010 r
101 0x65 01100101 e
101 0x65 01100101 e
77 0x4D 01001101 M
97 0x61 01100001 a
112 0x70 01110000 p
-111 0x91 10010001
67 0x43 01000011 C
15 0x0F 00001111
99 0x63 01100011 c
111 0x6F 01101111 o
109 0x6D 01101101 m
46 0x2E 00101110 .
100 0x64 01100100 d
105 0x69 01101001 i
103 0x67 01100111 g
117 0x75 01110101 u
97 0x61 01100001 a
103 0x67 01100111 g
101 0x65 01100101 e
46 0x2E 00101110 .
67 0x43 01000011 C
97 0x61 01100001 a
114 0x72 01110010 r
-110 0x92 10010010
4 0x04 00000100
110 0x6E 01101110 n
97 0x61 01100001 a
109 0x6D 01101101 m
101 0x65 01100101 e
3 0x03 00000011
97 0x61 01100001 a
103 0x67 01100111 g
101 0x65 01100101 e
96 0x60 01100000 `
7 0x07 00000111
100 0x64 01100100 d
105 0x69 01101001 i
103 0x67 01100111 g
117 0x75 01110101 u
97 0x61 01100001 a
103 0x67 01100111 g
101 0x65 01100101 e
-65 0xBF 10111111
90 0x5A 01011010 Z
针对 TreeMap
的处理,大致也可以分为三步:
首先,写入前置信息:
写入前置标志位
0x4D
(M
)写入
Map
的类型(字符串形式)
其次,遍历
Map.Entry
,并将其序列化Key
Value
最后,写入结束标志位
0x5A
(Z
)。
与 HashMap
不同之处时,这里写入了 Map
的类型信息。所以,相比来说 HashMap
更加轻量级。在做微服务接口的参数和返回结果时,可以优先考虑 HashMap
。
再谈实例对象
为了方便叙述,在 Hessian 协议解释与实战(四):数组与集合:首谈实例对象 中,对对象的处理做了简要的概述。到这里,让我们再来认识一下实例对象。
处理实例对象的序列化主要有
JavaSerializer
和BeanSerializer
。这两者的区别如下:
JavaSerializer
是通过反射获取实例对象的属性进行序列化。排除static
和transient
属性,对其他所有的属性进行递归序列化处理。
BeanSerializer
是遵循 POJI bean 的约定,扫描实例的所有方法,发现同时存在 Getter 和 Setter 方法的属性才进行序列化,它并不直接直接操作所有的属性。注意:BeanSerializer
将会无法处理 Getter 方法是以is
开头的boolean
属性,因为BeanSerializer
只认以get
开头的方法。
在 Java 8 中,其实默认使用的并不是这两个,而是 UnsafeSerializer
。它与 JavaSerializer
相似,都是通过反射获取类的属性列表;但是与 JavaSerializer
不同之处时, JavaSerializer
通过 Field
使用反射获取实例对象属性对应的值;而 UnsafeSerializer
是使用 sun.misc.Unsafe
来获取字段的“指针”(offset
),再通过“指针”获取实例对象属性对应的值。
另外,启用 UnsafeSerializer
的先决条件是能否获得 sun.misc.Unsafe
实例。如果可以获得 sun.misc.Unsafe
实例,则就会启用 UnsafeSerializer
。当然,也可以通过配置 com.caucho.hessian.unsafe
变量为 false
来禁用 UnsafeSerializer
。这里,还有一个例外:如果待序列化的类包含了 writeReplace()
方法,则就会启用 JavaSerializer
。
未完待续…… |