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

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

前段时间,翻译了 Hessian 2.0 的序列化协议,发布在了 Hessian 2.0 序列化协议(中文版)。但是,其中有很多言语不详之处。所以,接下来会用几篇文章来详细解释并实践一下 Hessian 序列化协议,以求做到知其然知其所以然。目录如下:

  1. Hessian 协议解释与实战(一):布尔、日期、浮点数与整数 — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。

  2. Hessian 协议解释与实战(二):长整型、二进制数据与 Null — 介绍长整数类型数据、二进制数据和 null 等三种类型的数据的处理。

  3. Hessian 协议解释与实战(三):字符串 — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多,处理细节也有繁琐,所以单独成篇来介绍。

  4. Hessian 源码分析(Java) — 开始第四篇分析之前,先来介绍一下 Hessian 的源码实现。方便后续展开说明。

  5. Hessian 协议解释与实战(四):数组与集合 — 铺垫了一些关于实例对象的处理,重点介绍关于数组和集合的相关处理。

  6. Hessian 协议解释与实战(五):对象与映射 — 重点介绍关于对象与映射的相关处理。

  7. 未完待续,敬请继续关注 "地瓜哥"博客网

在上一篇文章 Hessian 协议解释与实战(四):数组与集合 中研究了数组和集合的处理方式。接下来介绍对象和映射的处理。

基础工具方法

基础工具方法就不再赘述,请直接参考 Hessian 协议解释与实战(一):基础工具方法 中提到的几个方法。

另外,在 Hessian 协议解释与实战(四):数组与集合 中,又对一些方法做了扩展和更新。

下面,我们来看一看映射的处理。

映射

坦白讲,个人觉得 Hessian 2.0 序列化协议(中文版):映射 的协议定义写的非常模糊。倒是给的示例,还可圈可点。

相关代码实现

通过 Hessian 源码分析(Java) 可以指定, MapSerializer 是处理映射的 Serializer。所以,只需要关注一下 MapSerializer 就可以对映射的处理一目了然。结合代码,可以看出,映射的序列化流程大致如下:

  1. AbstractHessianOutput.writeMapBegin(String type) — 写入映射起始信息;

  2. 遍历 Map.Entry,并且是先序列化 Key,然后序列化 Value;

  3. AbstractHessianOutput.writeMapEnd() — 写入映射结束信息。

另外,在 Hessian 源码分析(Java) 中,也提到在 Hessian2Output 中实现了 AbstractHessianOutput 的接口。所以,只需要关注 Hessian2Output 对上述方法的实现即可。

Hessian2Output 中可以看出:

  1. Hessian2Output.writeMapBegin(String type) 有两个类型:

    1. 需要写入类型: M,前置标志位后是映射类型信息;

    2. 不需要写入类型: H,这里没有写入类型信息。

  2. 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

从结果上来看,跟我们上面的分析差不多,确定了一些细节:

  1. 首先,写入前置标志位 0x48H

  2. 其次,遍历 Map.Entry,并将其序列化

    1. Key

    2. Value

  3. 最后,写入结束标志位 0x5AZ)。

接下来再看看其他类型的 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 的处理,大致也可以分为三步:

  1. 首先,写入前置信息:

    1. 写入前置标志位 0x4DM

    2. 写入 Map 的类型(字符串形式)

  2. 其次,遍历 Map.Entry,并将其序列化

    1. Key

    2. Value

  3. 最后,写入结束标志位 0x5AZ)。

HashMap 不同之处时,这里写入了 Map 的类型信息。所以,相比来说 HashMap 更加轻量级。在做微服务接口的参数和返回结果时,可以优先考虑 HashMap

再谈实例对象

为了方便叙述,在 Hessian 协议解释与实战(四):数组与集合:首谈实例对象 中,对对象的处理做了简要的概述。到这里,让我们再来认识一下实例对象。

处理实例对象的序列化主要有 JavaSerializerBeanSerializer。这两者的区别如下:

  • JavaSerializer 是通过反射获取实例对象的属性进行序列化。排除 statictransient 属性,对其他所有的属性进行递归序列化处理。

  • 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

未完待续……