Hessian 源码分析(Java)

Hessian 源码分析(Java)

前面通过几篇文章,解释并实践了一下 Hessian 的序列化协议。文章目录如下:

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

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

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

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

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

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

该系列第四篇文章准备详细介绍一下 Hessian 对对象、链表以及 Map 等处理。但是,越调试代码,越发觉得应该先对 Hessian 的实现做一个源码分析。于是,就有了本文。

这里有几点需要声明一下:

  1. 在上面“解释与实战”系列文章中提到的代码就不再重复说明。

  2. 通过“解释与实战”系列文章,大家应该可以领略到,处理序列化有大量的细节。但是,本文并不打算涉及。本文重点是介绍 Hessian 的 Java 实现的架构蓝图。相当于给指明一条路,沿着这条路,大家就可以探索 Hessian 的各种细节。

  3. 本文的介绍,全部基于 Hessian 4.0.60 的源码。由于没有找到 Hessian 的仓库,D瓜哥从 Hessian 的网站下,下载了源码包,解压后发布在了 GitHub 上: Hessian — The source code of Hessian Library.

主要流程

作为一个序列化框架,最主要的功能就是序列化和反序列化。所以,这两个流程至关重要。

序列化流程

在“解释与实战”系列文章,都是通过调用 Hessian2Output 类中的各种 write 方法来完成实验的。在研究针对对象的序列化时,通过调试 Hessian2Output.writeObject(Object object) 发现,任意类型的序列化,都可以通过调用该方法来实现:在底层,根据需要格式化对象的类型信息,分发到不同的 Serializer 实现类来完成。那么,我们只要研究 Hessian2Output.writeObject(Object object) 方法的流程就了解序列化的整体流程。直接上流程图:

Hessian2Output writeObject

从整个流程,我们可以看出:

  1. 可以通过设置 SerializerFactory 来定制 Serializer 的创建;

  2. 可以向 SerializerFactory 添加自定义的 Serializer 实现,来完成对指定类型的自定义序列化格式。

  3. 在官方实现中,已经内置了多种 Serializer 实现:

    1. RemoteSerializer

    2. InetAddressSerializer

    3. WriteReplaceSerializer

    4. MapSerializer

    5. CollectionSerializer

    6. ArraySerializer

    7. ThrowableSerializer

    8. InputStreamSerializer

    9. IteratorSerializer

    10. CalendarSerializer

    11. EnumerationSerializer

    12. EnumSerializer

    13. AnnotationSerializer

  4. 在序列化对象时,对于对象的每个字段,逐个递归调用 Hessian2Output.writeObject(Object object) 方法,来完成序列化操作。

接下来,看一下反序列化的流程。

反序列化流程

以前做实验,都是研究序列化操作,没有针对反序列化做实验。但是处理过程应该与序列化类似,整个流程在 Hessian2Input.readObject()。流程如下:

Hessian2Input readObject

从流程图来看:

  1. 感觉反序列化的流程比序列化的流程要复杂不少!

  2. 先读取标志位,再根据标志位来触发不同流程的处理。当然,根据“解释与实战”系列文章应该也知道,有些标志位和数据是混装的,所以,这里的标志位也可能是数据。

  3. 跟序列化相似,反序列化也是通过递归调用来推进反序列化的进行的。

  4. 内置的反序列化器和序列化器竟然不是一一对应的,有些意外:

    1. CollectionDeserializer

    2. MapDeserializer

    3. IteratorDeserializer

    4. AnnotationDeserializer

    5. ObjectDeserializer

    6. ArrayDeserializer

    7. EnumerationDeserializer

    8. EnumDeserializer

    9. ClassDeserializer

基本流程已经了解了,我们来看一下其中一些重要“参与者”。

主要“参与者”

从上面的流程图中,可以看出主要逻辑涉及到如下几个类及其子类:

  1. AbstractHessianOutput

  2. AbstractHessianInput

  3. AbstractSerializerFactory

  4. Serializer

  5. Deserializer

下面对这些类,做一些简要概述:

AbstractHessianOutput

AbstractHessianOutput 类是 Hessian 序列化的基础,主要实现有两个:

  1. Hessian2Output — 支持 Hessian 2 协议。协议细节,请看 Hessian 2.0 序列化协议(中文版)

  2. HessianOutput — 支持 Hessian 1 协议。这个现在没见多少案例,本文不涉及。

hessian AbstractHessianOutput

AbstractHessianOutput 类主要作用是定义了一些列的 writeXXX 方法。这些方法在 Hessian2Output 得到了实现。针对 Java 基本类型以及字符串等的序列化实现,都在 Hessian2Output 类中。将在下文 Serializer 中提到的用于处理 Java 基本类型以及字符串等的序列化的 BasicSerializer,其实是在内部通过类型来分别调用了 Hessian2Output 类中的相关方法来实现的。

AbstractHessianInput

如果说 AbstractHessianOutput 类是 Hessian 序列化的基础,那么 AbstractHessianInput 就是 Hessian 反序列化的基础。同样,它的主要实现也有两个:

  1. Hessian2Input — 支持 Hessian 2 协议。协议细节,请看 Hessian 2.0 序列化协议(中文版)

  2. HessianInput — 支持 Hessian 1 协议。这个现在没见多少案例,本文不涉及。

hessian AbstractHessianInput

AbstractHessianOutput 相反, AbstractHessianInput 主要作用是定义了一些列的 readXXX 方法。这些方法在 Hessian2Input 得到了实现。针对 Java 基本类型以及字符串等的序列化实现,都在 Hessian2Output 类中。

AbstractSerializerFactory

AbstractSerializerFactory 及其子类主要负责控制序列化规则和管理 Serializer

hessian AbstractSerializerFactory

Serializer

可以说 Serializer 是 Hessian 序列化中最重要的类也不为过。Serializer 的子类也非常多:

  1. AbstractSerializer

  2. AnnotationSerializer

  3. ArraySerializer

  4. BasicSerializer — null、 八种基本类型、 StringStringBuilderDateNumberObject、 八种基本类型数组、 String 数组、 Object 数组等都是通过该类完成序列化。实际上,关于 null、 八种基本类型、 StringStringBuilderDate 等类型的处理,它是通过调用 Hessian2Output 提供的相关方法来完成的。

  5. ByteArraySerializer

  6. BeanSerializer

  7. CalendarSerializer

  8. ClassSerializer

  9. CollectionSerializer

  10. EnumerationSerializer

  11. EnumSerializer

  12. InetAddressSerializer

  13. InputStreamSerializer

  14. IteratorSerializer

  15. JavaSerializer

  16. JavaUnsharedSerializer

  17. LocaleSerializer

  18. MapSerializer

  19. ObjectHandleSerializer

  20. RemoteSerializer

  21. SqlDateSerializer

  22. StringValueSerializer

  23. UnsafeSerializer

  24. UnsafeUnsharedSerializer

由于类型太多,一些不是很重要的类没有画在类图中。

全部展示在类图里,类图就太过细长,看不清楚了。

hessian Serializer

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

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

  • BeanSerializer 是遵循 POJI bean 的约定,扫描实例的所有方法,发现同时存在 Getter 和 Setter 方法的属性才进行序列化,它并不直接直接操作所有的属性。注意: BeanSerializer 将会无法处理 Getter 方法是以 is 开头的 boolean 属性,因为 BeanSerializer 只认以 get 开头的方法。

Deserializer

Serializer 相似, Deserializer 的子类也非常多:

  1. AbstractDeserializer

  2. AbstractListDeserializer

  3. ArrayDeserializer

  4. CollectionDeserializer

  5. EnumerationDeserializer

  6. IteratorDeserializer

  7. BasicDeserializer

  8. EnumDeserializer

  9. AbstractStringValueDeserializer

  10. BigDecimalDeserializer

  11. FileDeserializer

  12. ObjectNameDeserializer

  13. StringValueDeserializer

  14. InputStreamDeserializer

  15. MBeanAttributeInfoDeserializer

  16. MBeanConstructorInfoDeserializer

  17. MBeanInfoDeserializer

  18. MBeanNotificationInfoDeserializer

  19. MBeanOperationInfoDeserializer

  20. MBeanParameterInfoDeserializer

  21. ObjectDeserializer

  22. ObjectInstanceDeserializer

  23. SqlDateDeserializer

  24. ValueDeserializer

  25. AbstractMapDeserializer

  26. AnnotationDeserializer

  27. BeanDeserializer

  28. ClassDeserializer

  29. JavaDeserializer

  30. StackTraceElementDeserializer

  31. MapDeserializer

  32. UnsafeDeserializer

hessian Deserializer

架构

上面单独介绍了一些类,多少有些“杂乱无章”。这里展示一张各个类之间关系的架构图,帮助大家理清各个类之间的联系:

hessian architecture

一些新发现

在梳理 Hessian 的代码实现,以及查阅资料时,有一些新的发现,有两点特别说明一下:

enum 的支持

对于 enum 的序列化和反序列化,主要是 EnumSerializerEnumDeserializer 来完成的。在序列化时,只是将其“name”序列化到结果中了,别没有序列化其属性信息。反序列化时,是根据“name”,调用其 valueOf 方法来查出其对应的实例。这里就有一个问题: 如果服务端升级版本,新增了一个枚举值,那么在低版本的客户端就不能识别,反序列化就会抛异常。这是一个不兼容的过程!

下面的代码对这种情况做了验证:

/**
 * 测试 enum 进行 Hessian 序列化
 *
 * @author D瓜哥 · https://www.diguage.com/
 */
@Test
public void testEnumOut() throws Throwable {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    Hessian2Output out = getHessian2Output(bos);

    // 测试序列化时,去掉这行代码的注释
    // 测试反序列化时,将这行代码注释掉
    // out.writeObject(Color.Green);
    out.close();
    byte[] result = bos.toByteArray();

    String base64Hessian = Base64.getEncoder()
                                 .encodeToString(result);

    System.out.println("\n== Color: " + base64Hessian + " ==");
    printBytes(result);
}

/**
 * 测试 enum 新增枚举的 Hessian 反序列化
 *
 * @author D瓜哥 · https://www.diguage.com/
 */
@Test
public void testEnumIn() throws Throwable {
    String base64 = "QzAtY29tLmRpZ3VhZ2UubWFyc2hhbC5oZ" +
        "XNzaW9uLkhlc3NpYW5UZXN0JENvbG9ykQRuYW1lYAVHcmVlbg==";
    byte[] bytes = Base64.getDecoder().decode(base64);
    ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
    Hessian2Input hessian = getHessian2Input(bis);
    Object object = hessian.readObject();
    System.out.println(object);
}

/**
 * @author D瓜哥 · https://www.diguage.com/
 */
public enum Color {
    Red("red", 0),
    // 测试序列化时,去掉这行代码的注释
    // 测试反序列化时,将这行代码注释掉
    // Green("green", 1),
    Blue("blue", 2);

    private String colorName;
    private int colorCode;

    Color(String name, int code) {
        this.colorName = name;
        this.colorCode = code;
    }
}

// -- 序列化的输出结果 --
== Color: QzAtY29tLmRpZ3VhZ2UubWFyc2hhbC5oZXNzaW9uL
          khlc3NpYW5UZXN0JENvbG9ykQRuYW1lYAVHcmVlbg== ==
// 为了排版,将结果的 Base64 字符串从中间分行,实际中间没有任何换行和空格。
.... 0 ~ 61 ....
  67 0x43 01000011 C
  48 0x30 00110000 0
  45 0x2D 00101101 -
  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 .
 109 0x6D 01101101 m
  97 0x61 01100001 a
 114 0x72 01110010 r
 115 0x73 01110011 s
 104 0x68 01101000 h
  97 0x61 01100001 a
 108 0x6C 01101100 l
  46 0x2E 00101110 .
 104 0x68 01101000 h
 101 0x65 01100101 e
 115 0x73 01110011 s
 115 0x73 01110011 s
 105 0x69 01101001 i
 111 0x6F 01101111 o
 110 0x6E 01101110 n
  46 0x2E 00101110 .
  72 0x48 01001000 H
 101 0x65 01100101 e
 115 0x73 01110011 s
 115 0x73 01110011 s
 105 0x69 01101001 i
  97 0x61 01100001 a
 110 0x6E 01101110 n
  84 0x54 01010100 T
 101 0x65 01100101 e
 115 0x73 01110011 s
 116 0x74 01110100 t
  36 0x24 00100100 $
  67 0x43 01000011 C
 111 0x6F 01101111 o
 108 0x6C 01101100 l
 111 0x6F 01101111 o
 114 0x72 01110010 r
-111 0x91 10010001
   4 0x04 00000100 
 110 0x6E 01101110 n
  97 0x61 01100001 a
 109 0x6D 01101101 m
 101 0x65 01100101 e
  96 0x60 01100000 `
   5 0x05 00000101 
  71 0x47 01000111 G
 114 0x72 01110010 r
 101 0x65 01100101 e
 101 0x65 01100101 e
 110 0x6E 01101110 n (1)
1从这里可以看出:对 enum 的序列化,只是将其“name”进行了序列化,并不包含属性值。

日志打印

在研究 Hessian 代码时,Hessian 也会打印一些日志。为了减少其外部依赖,Hessian 使用了 Java 内置在 JDK 中的日志框架:

Hessian 的代码
import java.util.logging.*;

public class Hessian2Input
  extends AbstractHessianInput
  implements Hessian2Constants
{
  private static final Logger log
    = Logger.getLogger(Hessian2Input.class.getName());
}

所以,使用 slf4j 打印日志时,一定要处理这些日志:

<!-- 在 pom.xml 中增加相关依赖 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>${slf4j.version}</version>
    <scope>runtime</scope>
</dependency>

<!-- 在 logback.xml 中增加相关配置 -->
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
    <resetJUL>true</resetJUL>
</contextListener>

这样 JDK 日志框架打印的日志就会被输出到 logback.xml 配置的位置了。相关原理介绍,请看 SLF4JBridgeHandler

未完待续

在研究 Hessian 源码时,发现了 Dubbo 魔改版的 Hessian 开源在了 GitHub 上: apache/dubbo-hessian-lite: Hessian Lite for Apache Dubbo。简单翻看了一下代码,结构和 Hessian 提供的源码几乎一模一样,只是修改了一下包名。看 PR 记录,有 30 多个。看来一些小细节应该有所改动。以后有机会对比一下两者的差异,看看从这些改动中能否发现一些值得学习或者注意的知识点。