序列化方式四——Hessian
介绍
Hessian是一个轻量级的、基于HTTP的RPC(远程过程调用)框架,由Resin开源提供。它使用一个简单的、基于二进制的协议来序列化对象,并通过HTTP进行传输。Hessian的设计目标是提供一种高效、可靠且易于使用的远程服务调用机制。
优缺点
优点
-
轻量级:Hessian协议简单,不依赖于特定的Java类库或API,使得它非常适合用于资源受限的环境,如移动设备。
-
高效性:Hessian使用二进制协议进行数据传输,相比基于文本的协议(如XML或JSON),它在序列化和反序列化方面具有更高的性能。
-
跨语言支持:虽然Hessian主要由Java实现,但它的协议是跨语言的,意味着其他语言也可以实现Hessian客户端或服务器。
-
与HTTP集成:Hessian基于HTTP协议,因此可以轻松地与现有的Web基础设施和工具集成,如Web服务器、代理服务器和缓存系统。
-
易于使用:Hessian的API简洁明了,使得开发者能够快速地构建和部署RPC服务。
缺点
-
HTTP开销:尽管Hessian使用二进制协议进行数据传输,但它仍然基于HTTP协议。这意味着每次调用都可能伴随着HTTP请求/响应的开销,包括连接建立、头部传输等。
-
不支持异步调用:Hessian原生不支持异步调用模式,这可能限制了它在某些高性能或响应性要求较高的场景中的应用。
-
安全性考虑:由于Hessian基于HTTP,因此可能需要考虑额外的安全措施来保护传输的数据,如使用HTTPS进行加密传输。
原理
远程服务的工作原理
-
定义服务接口:开发者首先定义远程服务的接口,这些接口将用于客户端和服务端之间的通信。
-
实现服务端:服务端实现这些接口,并提供具体的业务逻辑。Hessian服务端通常部署在Web服务器上,作为Servlet来处理客户端的请求。
-
生成客户端代理:客户端使用Hessian提供的代理工厂类(如
HessianProxyFactory
)来生成服务接口的代理对象。这个代理对象负责将方法调用转换为网络请求,并发送给服务端。 -
请求传输与响应:客户端通过HTTP请求将序列化后的方法调用参数发送给服务端。服务端接收到请求后,反序列化参数,执行相应的方法,并将结果序列化后返回给客户端。客户端再反序列化响应数据,得到方法调用的结果。
序列化&反序列化的工作原理
Hessian的序列化
Hessian的序列化过程主要依赖Hessian2Output
类。当需要将一个对象序列化为二进制流时,可以通过以下步骤:
-
创建一个
Hessian2Output
对象,传入一个OutputStream
,例如ByteArrayOutputStream
。 -
调用
writeObject
方法,将要序列化的对象作为参数传入。 -
调用
flush
方法,确保所有缓冲的数据都被写入到OutputStream
中。 -
获取
OutputStream
中的二进制流数据。
Hessian的反序列化
Hessian的反序列化过程主要依赖Hessian2Input
类。当需要从二进制流中反序列化一个对象时,可以通过以下步骤:
-
创建一个
Hessian2Input
对象,传入一个InputStream
,例如ByteArrayInputStream
。 -
调用
readObject
方法,从InputStream
中读取并反序列化对象。
序列化和反序列化的原理
Hessian的序列化和反序列化过程基于Field机制,这意味着它会根据对象的类结构,将对象的每个字段序列化为二进制数据,并存储在OutputStream
中。在反序列化时,它会根据类结构从InputStream
中读取数据,并将数据设置到对象的字段中。
序列化&反序列化demo
实体类
package com.zhz.test.serialization.entity;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
import java.util.Date;@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Student implements Serializable {private String name;private String sex;private int age;private Date birthday;
}
测试用例
@Testpublic void test() {Student student = Student.builder().age(18).birthday(new Date()).name("zhouhengzhe").sex("男").build();byte[] bytes;try {//hessian序列化bytes = serialize(student);System.out.println(bytes);} catch (Exception e) {throw new RuntimeException(e);}//反序列化try {Object deserialize = deserialize(bytes);System.out.println(deserialize);} catch (Exception e) {throw new RuntimeException(e);}}public static byte[] serialize(Object obj) throws Exception {ByteArrayOutputStream bos = new ByteArrayOutputStream();Hessian2Output out = new Hessian2Output(bos);out.writeObject(obj);out.flush();return bos.toByteArray();}public static Object deserialize(byte[] bytes) throws Exception {ByteArrayInputStream bis = new ByteArrayInputStream(bytes);Hessian2Input in = new Hessian2Input(bis);return in.readObject();}
远程服务demo
使用说明
-
实现 Serializable 接口:
- 要进行 Hessian 序列化,对象需要实现
java.io.Serializable
接口。如果不实现此接口,尝试序列化该对象时将会抛出java.io.NotSerializableException
异常。
- 要进行 Hessian 序列化,对象需要实现
-
serialVersionUID:
- 虽然实现了
Serializable
接口的类可以有一个serialVersionUID
字段,但在 Hessian 序列化中,serialVersionUID
实际上不是必须的。即使存在serialVersionUID
,修改其值也不会影响 Hessian 的反序列化过程。这是因为 Hessian 并不使用 Java 的序列化机制,而是基于自己的协议。
- 虽然实现了
-
序列化过程:
- 在 Hessian 中,复杂对象的所有属性都被存储在一个 Map 中进行序列化。这意味着如果父类和子类有同名的成员变量,那么在序列化时,Hessian 会先序列化子类,然后序列化父类。这意味着反序列化时,子类的同名成员变量将被父类的值覆盖。
-
注意事项:
-
如果你的对象有大量的属性或嵌套对象,可能需要考虑性能问题。虽然 Hessian 是轻量级的,但大量的数据仍然可能导致性能下降。
-
由于 Hessian 序列化时使用的是二进制格式,因此在反序列化时,接收方需要知道对象的类结构,以便正确解析数据。
-
-
使用示例:
-
在客户端,你可以使用
HessianProxyFactory
类来创建远程服务接口的代理对象。 -
在服务端,你可以使用
HessianServlet
来暴露服务,或者自己实现Hessian
的序列化/反序列化逻辑。
-
源码解析
首先分析Hessian2Output的初始化过程,源码如下:
/** 输出字节流对象*/
// 这一行注释说明`_os`是一个用于将序列化后的数据写入到输出流中的对象。
protected OutputStream _os;/*** 通过构造函数初始化Hessian2Output对象,并传入一个OutputStream对象,该对象用于在后续操作中写入序列化的数据。*/
public Hessian2Output(OutputStream os){init(os);
}/** 初始化*/
// 这一行注释说明`init`方法用于初始化Hessian2Output对象,包括重置其内部状态并设置`_os`。
public void init(OutputStream os){reset();_os = os;
}/** 重置所有的指针和引用*/
// 这一行注释说明`reset`方法用于重置Hessian2Output对象的内部状态,清除所有已存储的引用和指针,以确保每次序列化操作都从一个清晰的状态开始。
public void reset(){if (_refs != null) {_refs.clear(); // 如果_refs不为null,则清除其所有元素。_refCount = 0; // 将引用计数设置为0。}_classRefs.clear(); // 清除已序列化的类引用。_typeRefs = null; // 将类型引用设置为null。_offset = 0; // 初始化偏移量。_isPacket = false; // 初始化是否处理数据包标志。_isUnshared = false; // 初始化是否共享对象标志。
}
Hessian2Output
内部包含一个OutputStream
属性,该属性用于将对象序列化后的字节流写入到该对象中。在创建Hessian2Output
对象时,其构造方法主要执行两个操作:
-
1、重置所有与序列化相关的方法和属性,确保在序列化的过程中不会出现混乱或遗留状态;
-
2、初始化字节流对象,为后续的序列化操作做好准备。
writeObejct方法解析(序列化源码解析)
public void writeObject(Object object)throws IOException{/** 1.如果对象为空,则写入空对象 */if (object == null) {writeNull();return;}/** 2.根据对象的Class来获取序列化器 */Serializer serializer = findSerializerFactory().getObjectSerializer(object.getClass());/** 3.调用序列化器的writeObject进行对象序列化 */serializer.writeObject(object, this);
}
该方法相对简单,逻辑清晰。当传入的对象为空时,它会直接写入空数据;而当对象不为空时,它会根据对象的Class
信息来构造一个特定的序列化器对象,随后直接调用该序列化器的writeObject
方法进行序列化操作。
当对象为空时
/**
*定义了一个常量,表示字节数组缓存的大小,即8 * 1024字节,也就是8KB。
*/
public final static int SIZE = 8 * 1024;
/** 字节数组缓存,用于缓存序列化数据 */
private final byte []_buffer = new byte[SIZE];
/** 字节数组偏移量,表示当前在_buffer中的偏移量,即下一个要写入的位置 */
private int _offset;/**
*写入空对象
*<p/>
* 详细解析:用于将空对象写入缓存。首先检查是否有足够的空间来写入一个表示“空对象”的标识符(这里是字符'N')。
* 如果空间不足,调用flushBuffer方法将缓存中的数据写入输出流,并清空缓存。
* 然后,在_buffer中写入'N'字符,并更新_offset。
*/
public void writeNull() throws IOException{int offset = _offset;byte []buffer = _buffer;if (SIZE <= offset + 16) {/** 如果字节数组缓存不足,则将缓存数据写入到OutputStream中并清除缓存*/flushBuffer();offset = _offset;}/** 写入字符串N表示当前的对象为空对象 */buffer[offset++] = 'N';/** 更新偏移量*/_offset = offset;
}/**
* 清除缓存,并将缓存数据写入输出字节流中
*
* <p/>
* 这个方法用于清除缓存,并将缓存中的数据写入输出流。
* 如果_isPacket为false且_offset大于0,表示有数据需要写入输出流,然后重置_offset为0,并将数据写入输出流。
* 如果_isPacket为true且_offset大于4,表示需要处理数据包格式,并在数据前添加4字节的长度信息。然后,将数据写入输出流,并重置_offset为4。
* 最后,清除数据包的标识(将前三字节设置为0x00、0x56、0x56)。
*
*/
public final void flushBuffer()throws IOException{int offset = _offset;OutputStream os = _os;if (! _isPacket && offset > 0) {_offset = 0;if (os != null)os.write(_buffer, 0, offset);}else if (_isPacket && offset > 4) {int len = offset - 4;_buffer[0] |= (byte) 0x80;_buffer[1] = (byte) (0x7e);_buffer[2] = (byte) (len >> 8);_buffer[3] = (byte) (len);_offset = 4;if (os != null)os.write(_buffer, 0, offset);_buffer[0] = (byte) 0x00;_buffer[1] = (byte) 0x56;_buffer[2] = (byte) 0x56;_buffer[3] = (byte) 0x56;}
}
writeNull
方法逻辑相对简单,主要就是在_buffer
字节数组中写入字符’N’来标识空对象。Hessian2Output
对象内部有一个字节数组缓存_buffer
和一个数组写入偏移量_offset
变量。
该字节数组缓存的大小为8KB(即8 * 1024个字节),用于临时存储序列化的字节流。在序列化过程中,数据首先写入缓存中。当缓存的容量不足以容纳更多数据时,会调用flushBuffer
方法将缓存中的数据写入输出流,并清除缓存。这种机制确保了数据的分批次处理和输出,同时也确保了内存的有效利用。
当对象不为空时
当对象不为空时,序列化的方式需要根据对象的类型来选择。不同的对象类型,如String
、List
以及用户自定义的实体类,通常会有不同的序列化方式。为了支持这些不同的序列化方式,定义了一个Serializer
接口,该接口应包含相应类型对象序列化的具体实现。
例如,String
类型可能需要一个将字符串转换为字节序列的序列化方法,而List
类型可能需要一个将列表元素逐个序列化并合并的序列化方法。对于用户自定义的实体类,也需要提供相应的序列化方法。
通过实现Serializer
接口,可以为不同的对象类型提供合适的序列化方式,确保在序列化过程中能够正确地将对象转换为可存储或传输的格式。
对象序列化的接口为Serializer,定义如下:
public interface Serializer {public void writeObject(Object obj, AbstractHessianOutput out)throws IOException;
}
具体的实现类比较多,针对不同的类型有不同的实现子类,
基本数据类型序列化
对于常用的基本数据类型及其数组类型,Hessian
框架提供了BasicSerializer
这一序列化器,用于这些类型的序列化。这个序列化器在ContextSerializerFactory
类中被初始化并注册。
在ContextSerializerFactory
类中,通过相应的代码,基本数据类型的序列化器被注册到序列化器工厂中,以便在序列化过程中能够正确地识别和序列化这些基本数据类型及其数组类型。
addBasic(void.class, "void", BasicSerializer.NULL);addBasic(Boolean.class, "boolean", BasicSerializer.BOOLEAN);
addBasic(Byte.class, "byte", BasicSerializer.BYTE);
addBasic(Short.class, "short", BasicSerializer.SHORT);
addBasic(Integer.class, "int", BasicSerializer.INTEGER);
addBasic(Long.class, "long", BasicSerializer.LONG);
addBasic(Float.class, "float", BasicSerializer.FLOAT);
addBasic(Double.class, "double", BasicSerializer.DOUBLE);
addBasic(Character.class, "char", BasicSerializer.CHARACTER_OBJECT);
addBasic(String.class, "string", BasicSerializer.STRING);
addBasic(Object.class, "object", BasicSerializer.OBJECT);
addBasic(java.util.Date.class, "date", BasicSerializer.DATE);addBasic(boolean.class, "boolean", BasicSerializer.BOOLEAN);
addBasic(byte.class, "byte", BasicSerializer.BYTE);
addBasic(short.class, "short", BasicSerializer.SHORT);
addBasic(int.class, "int", BasicSerializer.INTEGER);
addBasic(long.class, "long", BasicSerializer.LONG);
addBasic(float.class, "float", BasicSerializer.FLOAT);
addBasic(double.class, "double", BasicSerializer.DOUBLE);
addBasic(char.class, "char", BasicSerializer.CHARACTER);addBasic(boolean[].class, "[boolean", BasicSerializer.BOOLEAN_ARRAY);
addBasic(byte[].class, "[byte", BasicSerializer.BYTE_ARRAY);
_staticSerializerMap.put(byte[].class.getName(), ByteArraySerializer.SER);
addBasic(short[].class, "[short", BasicSerializer.SHORT_ARRAY);
addBasic(int[].class, "[int", BasicSerializer.INTEGER_ARRAY);
addBasic(long[].class, "[long", BasicSerializer.LONG_ARRAY);
addBasic(float[].class, "[float", BasicSerializer.FLOAT_ARRAY);
addBasic(double[].class, "[double", BasicSerializer.DOUBLE_ARRAY);
addBasic(char[].class, "[char", BasicSerializer.CHARACTER_ARRAY);
addBasic(String[].class, "[string", BasicSerializer.STRING_ARRAY);
addBasic(Object[].class, "[object", BasicSerializer.OBJECT_ARRAY);
虽然基本数据类型都是通过BasicSerializer进行序列化,但是不同的类型都是有不同的type的,BasicSerializer根据不同的type进行不同的序列化逻辑处理。具体的序列化逻辑如下:
/** 这是一个处理各种数据类型序列化的方法,专门用于Hessian序列化库。 */
public void writeObject(Object obj, AbstractHessianOutput out)
throws IOException {// 根据对象的类型,执行不同的序列化操作switch (_code) {// 处理布尔类型case BOOLEAN:out.writeBoolean(((Boolean) obj).booleanValue());break;// 处理byte, short, 和 int类型case BYTE:case SHORT:case INTEGER:out.writeInt(((Number) obj).intValue());break;// 处理long类型case LONG:out.writeLong(((Number) obj).longValue());break;// 处理float和double类型case FLOAT:case DOUBLE:out.writeDouble(((Number) obj).doubleValue());break;// 处理char和Character类型case CHARACTER:case CHARACTER_OBJECT:out.writeString(String.valueOf(obj));break;// 处理String类型case STRING:out.writeString((String) obj);break;// 处理StringBuilder类型case STRING_BUILDER:out.writeString(((StringBuilder) obj).toString());break;// 处理Date类型case DATE:out.writeUTCDate(((Date) obj).getTime());break;// 处理布尔数组类型case BOOLEAN_ARRAY:// 如果对象已经存在于输出流中,则不再序列化if (out.addRef(obj)) return;boolean []data = (boolean []) obj;boolean hasEnd = out.writeListBegin(data.length, "[boolean");for (int i = 0; i < data.length; i++)out.writeBoolean(data[i]);// 结束列表if (hasEnd)out.writeListEnd();break;// 处理byte数组类型case BYTE_ARRAY:byte []data = (byte []) obj;out.writeBytes(data, 0, data.length);break;// 处理short数组类型case SHORT_ARRAY:if (out.addRef(obj)) return;short []data = (short []) obj;boolean hasEnd = out.writeListBegin(data.length, "[short");for (int i = 0; i < data.length; i++)out.writeInt(data[i]);if (hasEnd)out.writeListEnd();break;// 处理int数组类型case INTEGER_ARRAY:if (out.addRef(obj)) return;int []data = (int []) obj;boolean hasEnd = out.writeListBegin(data.length, "[int");for (int i = 0; i < data.length; i++)out.writeInt(data[i]);if (hasEnd)out.writeListEnd();break;// 处理long数组类型case LONG_ARRAY:if (out.addRef(obj)) return;long []data = (long []) obj;boolean hasEnd = out.writeListBegin(data.length, "[long");for (int i = 0; i < data.length; i++)out.writeLong(data[i]);if (hasEnd)out.writeListEnd();break;// 处理float数组类型case FLOAT_ARRAY:if (out.addRef(obj)) return;float []data = (float []) obj;boolean hasEnd = out.writeListBegin(data.length, "[float");for (int i = 0; i < data.length; i++)out.writeDouble(data[i]);if (hasEnd)out.writeListEnd();break;// 处理double数组类型case DOUBLE_ARRAY:if (out.addRef(obj)) return;double []data = (double []) obj;boolean hasEnd = out.writeListBegin(data.length, "[double");for (int i = 0; i < data.length; i++)out.writeDouble(data[i]);if (hasEnd)out.writeListEnd();break;// 处理String数组类型case STRING_ARRAY:if (out.addRef(obj)) return;String []data = (String []) obj;boolean hasEnd = out.writeListBegin(data.length, "[string");for (int i = 0; i < data.length; i++) {out.writeString(data[i]);}if (hasEnd)out.writeListEnd();break;// 处理char数组类型case CHARACTER_ARRAY:char []data = (char []) obj;out.writeString(data, 0, data.length);break;// 处理Object数组类型case OBJECT_ARRAY:if (out.addRef(obj)) return;Object []data = (Object []) obj;boolean hasEnd = out.writeListBegin(data.length, "[object");for (int i = 0; i < data.length; i++) {out.writeObject(data[i]);}if (hasEnd)out.writeListEnd();break;// 处理null类型case NULL:out.writeNull();break;// 处理Object类型case OBJECT:ObjectHandleSerializer.SER.writeObject(obj, out);break;// 处理特殊的字节、短整型、浮点数字符句柄类型case BYTE_HANDLE:out.writeObject(new ByteHandle((Byte) obj));break;case SHORT_HANDLE:out.writeObject(new ShortHandle((Short) obj));break;case FLOAT_HANDLE:out.writeObject(new FloatHandle((Float) obj));break;// 如果对象的类型不在上述列表中,则抛出异常default:throw new RuntimeException(_code + " unknown code for " + obj.getClass());}
}
代码逻辑清晰,对于不同类型的对象,通过类型判断调用对应的write
方法。对于数组类型,它会在写入数组数据之前和之后分别调用writeListBegin
和writeListEnd
方法,以标记数组的起始和结束。
如字符串的序列化方法writeString源码如下:
/*** 序列化字符串的函数* @param value 需要序列化的字符串* @throws IOException 如果在序列化过程中发生I/O错误,则抛出此异常*/
public void writeString(String value) throws IOException {int offset = _offset; // 当前的偏移量byte []buffer = _buffer; // 当前的缓冲区/*** 如果缓冲区已满(即,剩余空间不足以容纳接下来的数据),则清空缓冲区,并更新偏移量*/if (SIZE <= offset + 16) {flushBuffer();offset = _offset;}/*** 如果要序列化的字符串为空,则写入一个'N'标记表示空字符串*/if (value == null) {buffer[offset++] = (byte) 'N';_offset = offset;} else {int length = value.length(); // 字符串的长度int strOffset = 0; // 字符串的起始偏移量/*** 如果字符串的长度超过32K,则将其拆分成多个32K大小的字符串进行序列化*/while (length > 0x8000) {int sublen = 0x8000; // 子字符串的长度// 如果缓冲区已满,则清空缓冲区,并更新偏移量if (SIZE <= offset + 16) {flushBuffer();offset = _offset;}// 检查最后一个字符是否为高代理字符,如果是,则减少子字符串的长度char tail = value.charAt(strOffset + sublen - 1);if (0xd800 <= tail && tail <= 0xdbff)sublen--;/*** 写入子字符串的标记('R')、长度和具体的数据*/buffer[offset + 0] = (byte) BC_STRING_CHUNK;buffer[offset + 1] = (byte) (sublen >> 8);buffer[offset + 2] = (byte) (sublen);_offset = offset + 3;printString(value, strOffset, sublen); // 写入子字符串的数据length -= sublen; // 更新剩余长度strOffset += sublen; // 更新偏移量}// 如果字符串的长度小于32K,则直接写入offset = _offset;// 如果缓冲区已满,则清空缓冲区,并更新偏移量if (SIZE <= offset + 16) {flushBuffer();offset = _offset;}if (length <= STRING_DIRECT_MAX) {buffer[offset++] = (byte) (BC_STRING_DIRECT + length);} else if (length <= STRING_SHORT_MAX) {buffer[offset++] = (byte) (BC_STRING_SHORT + (length >> 8));buffer[offset++] = (byte) (length);} else {buffer[offset++] = (byte) ('S');buffer[offset++] = (byte) (length >> 8);buffer[offset++] = (byte) (length);}_offset = offset;printString(value, strOffset, length); // 写入剩余的字符串数据}
}
在写入字符串时,首先会写入一个标记,表示字符串的类型。如果字符串过长,会将其拆分成多个子字符串,并使用“R”标记表示子字符串,而非子字符串则使用“S”标记。接着,会写入字符串的长度。最后,使用printString
方法将字符串的实际数据写入字节数组中。
printString
方法的逻辑是遍历字符串的每一位,将每个字符依次写入字节数组中。源码如下:
public void printString(String v, int strOffset, int length) throws IOException {int offset = _offset; // 获取当前的偏移量byte []buffer = _buffer; // 获取当前的缓冲区for (int i = 0; i < length; i++) { // 遍历字符串中的每个字符// 如果缓冲区已满,则清空缓冲区,并更新偏移量if (SIZE <= offset + 16) {_offset = offset;flushBuffer(); // 清空缓冲区offset = _offset; // 更新偏移量}char ch = v.charAt(i + strOffset); // 获取当前字符// 如果字符的Unicode值小于0x80,则直接将该字符的字节值放入缓冲区if (ch < 0x80) {buffer[offset++] = (byte) ch;}// 如果字符的Unicode值在0x80到0x7ff之间,则使用两个字节的UTF-8编码表示else if (ch < 0x800) {buffer[offset++] = (byte) (0xc0 + ((ch >> 6) & 0x1f));buffer[offset++] = (byte) (0x80 + (ch & 0x3f));}// 如果字符的Unicode值大于0x7ff,则使用三个字节的UTF-8编码表示else {buffer[offset++] = (byte) (0xe0 + ((ch >> 12) & 0xf));buffer[offset++] = (byte) (0x80 + ((ch >> 6) & 0x3f));buffer[offset++] = (byte) (0x80 + (ch & 0x3f));}}_offset = offset; // 更新偏移量
}
解释:
-
该方法接收一个字符串
v
,一个偏移量strOffset
和一个长度length
。 -
它遍历字符串中的每个字符,并使用UTF-8编码将其转换为字节。
-
如果缓冲区已满(即,无法再容纳16个字节),它会清空缓冲区并更新偏移量。
-
最后,它更新偏移量以反映已写入的字节数。
对于其他基本数据类型,其写入逻辑与字符串相似,都是先写入标记,然后直接写入数据。例如,Long类型使用"L"作为标记,Double类型使用"D"作为标记,而Int类型则使用"I"作为标记**。**这种序列化方式与JDK的序列化方法相比,在数据类型方面显著减少了数据量。Hessian通过仅使用一个字符来表示基本数据类型,而JDK序列化则需要序列化类的全路径。例如,对于String类型,Hessian只需写入"S"作为标记,而JDK序列化则需要写入完整的类路径"java.lang.String"。
自定义数据类型序列化
自定义数据类型的序列化是通过UnsafeSerializer进行的序列化,源码如下:
public void writeObject(Object obj, AbstractHessianOutput out)throws IOException {if (out.addRef(obj)) {return;}Class<?> cl = obj.getClass();/** 写入Object类型开始标记 将类名存入Map中,并记录该类的引用次数 */int ref = out.writeObjectBegin(cl.getName());/** 如果引用次数大于0则表示已经被引用过*/if (ref >= 0) {/** 直接写入实例 */writeInstance(obj, out);}/** 值为-1表示第一次引用 */else if (ref == -1) {/** 写入类的定义,依次写入属性个数和所有属性的名称 */writeDefinition20(out);/** 写入类的名称 */out.writeObjectBegin(cl.getName());/** 写入实例 */writeInstance(obj, out);}else {writeObject10(obj, out);}}
主要逻辑为:
-
获取Class的引用次数:首先,检查对象所属的类是否已经被序列化过。这通常是通过检查输出流中的引用计数映射来完成的。
-
判断是否需要重新解析:如果类已经被引用过,那么不需要重新解析类的定义,直接写入对象的实例即可。
-
写入类的定义(如果需要):如果类没有被引用过,那么需要先写入类的定义,包括属性的个数和所有属性的名称。
-
写入类的名称:无论类是否已经被引用过,都需要写入类的名称。
-
写入实例对象:最后,执行
writeInstance
方法将实例对象写入输出流。
writeDefinition20方法源码如下:
/*** 写入类的定义** @param outHessian输出流对象,用于将类的定义写入* @throws IOException可能抛出的异常*/
private void writeDefinition20(AbstractHessianOutput out) throws IOException {/*** 写入类的属性个数** @param length属性个数*/out.writeClassFieldLength(_fields.length); // 写入属性个数到输出流中/*** 依次写入属性的名称** @param field当前处理的属性*/for (int i = 0; i < _fields.length; i++) {Field field = _fields[i]; // 获取当前处理的属性out.writeString(field.getName()); // 将属性的名称写入输出流中}
}
writeObjectBegin方法源码如下:
/** 开始写入对象*/
public int writeObjectBegin(String type) throws IOException {// 获取类的引用次数int newRef = _classRefs.size();int ref = _classRefs.put(type, newRef, false);// 如果引用次数发生了变化if (newRef != ref) {// 如果缓冲区已满,先清空缓冲区if (SIZE < _offset + 32)flushBuffer();// 如果引用次数小于等于直接对象最大数if (ref <= OBJECT_DIRECT_MAX) {// 写入直接对象的标记和引用次数_buffer[_offset++] = (byte) (BC_OBJECT_DIRECT + ref);} else {// 写入对象引用类型的标记_buffer[_offset++] = (byte) 'O';// 写入引用次数writeInt(ref);}// 返回引用次数return ref;} else {// 如果缓冲区已满,先清空缓冲区if (SIZE < _offset + 32)flushBuffer();// 写入类类型的标记_buffer[_offset++] = (byte) 'C';// 写入类名writeString(type);// 返回-1,表示这是一个新创建的类引用return -1;}
}
writeInstance方法源码如下:
/** 写入对象实例 */
final public void writeInstance(Object obj, AbstractHessianOutput out) throws IOException {try {/** 获取所有属性序列化器对象 */FieldSerializer []fieldSerializers = _fieldSerializers;int length = fieldSerializers.length;// 遍历所有属性序列化器,并对每个属性执行序列化方法for (int i = 0; i < length; i++) {fieldSerializers[i].serialize(out, obj);}} catch (RuntimeException e) {// 如果捕获到RuntimeException,则抛出一个新的RuntimeException,其中包含原始异常信息、类名和对象信息throw new RuntimeException(e.getMessage() + "\n class: " + obj.getClass().getName() + " (object=" + obj + ")", e);} catch (IOException e) {// 如果捕获到IOException,则抛出一个新的IOExceptionWrapper,其中包含原始异常信息、类名和对象信息throw new IOExceptionWrapper(e.getMessage() + "\n class: " + obj.getClass().getName() + " (object=" + obj + ")", e);}}
写入实例的整体逻辑相当直接且简洁。针对每个字段,我们都有一个专门的序列化器。通过遍历所有属性,我们调用与之对应的序列化器的序列化方法。如果某个属性是自定义类型,那么我们会继续遍历该属性的类,直到我们处理到的是基本数据类型属性为止。这样的方法确保了所有属性都能被适当地序列化。
反序列化源码解析
和序列化相反,反序列化是通过输入流Hessian2Input对象的readObject方法来实现的,源码如下:
/** 反序列化对象*/
public Object readObject() throws IOException
{/** 调用read()方法读取字节,第一次就读取第一个字节 */int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read();/** 判断字节对应的标记类型,执行对应的解析方法 */switch (tag) {case 'N':return null;case 'T':return Boolean.valueOf(true);case 'F':return Boolean.valueOf(false);// direct integercase 0x80: case 0x81: case 0x82: case 0x83:case 0x84: case 0x85: case 0x86: case 0x87:case 0x88: case 0x89: case 0x8a: case 0x8b:case 0x8c: case 0x8d: case 0x8e: case 0x8f:case 0x90: case 0x91: case 0x92: case 0x93:case 0x94: case 0x95: case 0x96: case 0x97:case 0x98: case 0x99: case 0x9a: case 0x9b:case 0x9c: case 0x9d: case 0x9e: case 0x9f:case 0xa0: case 0xa1: case 0xa2: case 0xa3:case 0xa4: case 0xa5: case 0xa6: case 0xa7:case 0xa8: case 0xa9: case 0xaa: case 0xab:case 0xac: case 0xad: case 0xae: case 0xaf:case 0xb0: case 0xb1: case 0xb2: case 0xb3:case 0xb4: case 0xb5: case 0xb6: case 0xb7:case 0xb8: case 0xb9: case 0xba: case 0xbb:case 0xbc: case 0xbd: case 0xbe: case 0xbf:return Integer.valueOf(tag - BC_INT_ZERO);/* byte int */case 0xc0: case 0xc1: case 0xc2: case 0xc3:case 0xc4: case 0xc5: case 0xc6: case 0xc7:case 0xc8: case 0xc9: case 0xca: case 0xcb:case 0xcc: case 0xcd: case 0xce: case 0xcf:return Integer.valueOf(((tag - BC_INT_BYTE_ZERO) << 8) + read());/* short int */case 0xd0: case 0xd1: case 0xd2: case 0xd3:case 0xd4: case 0xd5: case 0xd6: case 0xd7:return Integer.valueOf(((tag - BC_INT_SHORT_ZERO) << 16)+ 256 * read() + read());case 'I':return Integer.valueOf(parseInt());// direct longcase 0xd8: case 0xd9: case 0xda: case 0xdb:case 0xdc: case 0xdd: case 0xde: case 0xdf:case 0xe0: case 0xe1: case 0xe2: case 0xe3:case 0xe4: case 0xe5: case 0xe6: case 0xe7:case 0xe8: case 0xe9: case 0xea: case 0xeb:case 0xec: case 0xed: case 0xee: case 0xef:return Long.valueOf(tag - BC_LONG_ZERO);/* byte long */case 0xf0: case 0xf1: case 0xf2: case 0xf3:case 0xf4: case 0xf5: case 0xf6: case 0xf7:case 0xf8: case 0xf9: case 0xfa: case 0xfb:case 0xfc: case 0xfd: case 0xfe: case 0xff:return Long.valueOf(((tag - BC_LONG_BYTE_ZERO) << 8) + read());/* short long */case 0x38: case 0x39: case 0x3a: case 0x3b:case 0x3c: case 0x3d: case 0x3e: case 0x3f:return Long.valueOf(((tag - BC_LONG_SHORT_ZERO) << 16) + 256 * read() + read());case BC_LONG_INT:return Long.valueOf(parseInt());case 'L':return Long.valueOf(parseLong());case BC_DOUBLE_ZERO:return Double.valueOf(0);case BC_DOUBLE_ONE:return Double.valueOf(1);case BC_DOUBLE_BYTE:return Double.valueOf((byte) read());case BC_DOUBLE_SHORT:return Double.valueOf((short) (256 * read() + read()));case BC_DOUBLE_MILL:{int mills = parseInt();return Double.valueOf(0.001 * mills);}case 'D':return Double.valueOf(parseDouble());case BC_DATE:return new Date(parseLong());case BC_DATE_MINUTE:return new Date(parseInt() * 60000L);case BC_STRING_CHUNK:case 'S':{_isLastChunk = tag == 'S';_chunkLength = (read() << 8) + read();_sbuf.setLength(0);parseString(_sbuf);return _sbuf.toString();}case 0x00: case 0x01: case 0x02: case 0x03:case 0x04: case 0x05: case 0x06: case 0x07:case 0x08: case 0x09: case 0x0a: case 0x0b:case 0x0c: case 0x0d: case 0x0e: case 0x0f:case 0x10: case 0x11: case 0x12: case 0x13:case 0x14: case 0x15: case 0x16: case 0x17:case 0x18: case 0x19: case 0x1a: case 0x1b:case 0x1c: case 0x1d: case 0x1e: case 0x1f:{_isLastChunk = true;_chunkLength = tag - 0x00;int data;_sbuf.setLength(0);parseString(_sbuf);return _sbuf.toString();}case 0x30: case 0x31: case 0x32: case 0x33:{_isLastChunk = true;_chunkLength = (tag - 0x30) * 256 + read();_sbuf.setLength(0);parseString(_sbuf);return _sbuf.toString();}case BC_BINARY_CHUNK:case 'B':{_isLastChunk = tag == 'B';_chunkLength = (read() << 8) + read();int data;ByteArrayOutputStream bos = new ByteArrayOutputStream();while ((data = parseByte()) >= 0)bos.write(data);return bos.toByteArray();}case 0x20: case 0x21: case 0x22: case 0x23:case 0x24: case 0x25: case 0x26: case 0x27:case 0x28: case 0x29: case 0x2a: case 0x2b:case 0x2c: case 0x2d: case 0x2e: case 0x2f:{_isLastChunk = true;int len = tag - 0x20;_chunkLength = 0;byte []data = new byte[len];for (int i = 0; i < len; i++)data[i] = (byte) read();return data;}case 0x34: case 0x35: case 0x36: case 0x37:{_isLastChunk = true;int len = (tag - 0x34) * 256 + read();_chunkLength = 0;byte []buffer = new byte[len];for (int i = 0; i < len; i++) {buffer[i] = (byte) read();}return buffer;}case BC_LIST_VARIABLE:{// variable length listString type = readType();return findSerializerFactory().readList(this, -1, type);}case BC_LIST_VARIABLE_UNTYPED:{return findSerializerFactory().readList(this, -1, null);}case BC_LIST_FIXED:{// fixed length listsString type = readType();int length = readInt();Deserializer reader;reader = findSerializerFactory().getListDeserializer(type, null);return reader.readLengthList(this, length);}case BC_LIST_FIXED_UNTYPED:{// fixed length listsint length = readInt();Deserializer reader;reader = findSerializerFactory().getListDeserializer(null, null);return reader.readLengthList(this, length);}// compact fixed listcase 0x70: case 0x71: case 0x72: case 0x73:case 0x74: case 0x75: case 0x76: case 0x77:{// fixed length listsString type = readType();int length = tag - 0x70;Deserializer reader;reader = findSerializerFactory().getListDeserializer(type, null);return reader.readLengthList(this, length);}// compact fixed untyped listcase 0x78: case 0x79: case 0x7a: case 0x7b:case 0x7c: case 0x7d: case 0x7e: case 0x7f:{// fixed length listsint length = tag - 0x78;Deserializer reader;reader = findSerializerFactory().getListDeserializer(null, null);return reader.readLengthList(this, length);}case 'H':{return findSerializerFactory().readMap(this, null);}case 'M':{String type = readType();return findSerializerFactory().readMap(this, type);}case 'C':{readObjectDefinition(null);return readObject();}case 0x60: case 0x61: case 0x62: case 0x63:case 0x64: case 0x65: case 0x66: case 0x67:case 0x68: case 0x69: case 0x6a: case 0x6b:case 0x6c: case 0x6d: case 0x6e: case 0x6f:{int ref = tag - 0x60;if (_classDefs.size() <= ref)throw error("No classes defined at reference '"+ Integer.toHexString(tag) + "'");ObjectDefinition def = _classDefs.get(ref);//读取实例对象return readObjectInstance(null, def);}case 'O':{int ref = readInt();if (_classDefs.size() <= ref)throw error("Illegal object reference #" + ref);ObjectDefinition def = _classDefs.get(ref);return readObjectInstance(null, def);}case BC_REF:{int ref = readInt();return _refs.get(ref);}default:if (tag < 0)throw new EOFException("readObject: unexpected end of file");elsethrow error("readObject: unknown code " + codeName(tag));}
}
主要逻辑是先读取标记,随后依据这些标记判断其对应的数据类型。一旦确定了类型,例如,若解析到的类型为’C’,则表示该数据为对象类型,于是系统会进一步调用readObjectDefinition
方法进行类定义的反解析,完成后,再通过readObject
方法递归地解析后续内容。整个流程是依据数据类型进行条件判断,并执行相应的反解析操作。
readObejctDefintion方法源码如下:
/*** 读取类的定义*/
private void readObjectDefinition(Class<?> cl) throws IOException {/*** 获取类型*/String type = readString(); // 读取类型字符串int len = readInt(); // 读取属性数量SerializerFactory factory = findSerializerFactory(); // 找到序列化工厂对象// 获取对应类型的反序列化器Deserializer reader = factory.getObjectDeserializer(type, null);// 创建属性数组,用于存储反序列化后的属性值Object[] fields = reader.createFields(len);// 创建属性名称数组String[] fieldNames = new String[len];// 依次读取每个属性的名称和值for (int i = 0; i < len; i++) {String name = readString(); // 读取属性名称fields[i] = reader.createField(name); // 根据属性名称创建对应的属性值对象fieldNames[i] = name; // 将属性名称存储到数组中}// 根据读取到的类型、反序列化器、属性值和属性名称,构造ObjectDefinition对象ObjectDefinition def = new ObjectDefinition(type, reader, fields, fieldNames);_classDefs.add(def); // 将构造好的ObjectDefinition对象添加到_classDefs列表中
}
主要功能是读取并反序列化一个类的定义。它首先读取类型和属性的数量,然后获取相应的反序列化器,并创建用于存储属性和属性名称的数组。接下来,它循环读取每个属性的名称,并使用反序列化器为每个属性创建对应的属性值对象。最后,它使用读取到的信息构造一个ObjectDefinition
对象,并将其添加到一个列表中。
接着会执行到readObjectInstance方法,源码如下:
/*** 根据给定的类和对象定义反序列化一个对象实例。** @param cl 要反序列化对象的类* @param def 对象定义,包含反序列化所需的信息* @return 反序列化后的对象实例* @throws IOException 如果在反序列化过程中发生I/O错误*/
private Object readObjectInstance(Class<?> cl, ObjectDefinition def) throws IOException {// 从对象定义中获取对象的类型String type = def.getType();// 从对象定义中获取反序列化器Deserializer reader = def.getReader();// 从对象定义中获取字段信息(可能是字段的值或者其他相关信息)Object[] fields = def.getFields();// 查找序列化工厂,该工厂可能提供特定的反序列化器SerializerFactory factory = findSerializerFactory();// 检查给定的类与反序列化器期望的类是否匹配if (cl != reader.getType() && cl != null) {// 如果不匹配且给定的类不为null,则从工厂中获取一个新的反序列化器reader = factory.getObjectDeserializer(type, cl);// 使用新的反序列化器执行反序列化操作,并传入字段名数组以获取正确的字段顺序return reader.readObject(this, def.getFieldNames());} else {// 如果匹配或给定的类为null,则直接使用当前的反序列化器执行反序列化操作,并传入字段信息数组return reader.readObject(this, fields);}
}
首先获取对象定义中的类型、反序列化器和字段信息。然后,它查找一个序列化工厂,该工厂可能用于提供特定类型的反序列化器。接下来,方法检查给定的类是否与反序列化器期望的类匹配。如果不匹配(且给定的类不为null),则从工厂中获取一个新的反序列化器,并使用它来执行反序列化操作。如果匹配或给定的类为null,则直接使用当前的反序列化器执行反序列化操作。最后,方法返回反序列化后的对象实例。
然后执行反序列化器的readObejct方法,执行的是UnsafeDeserializer的readObject方法,源码如下:
/*** 读取并反序列化一个对象。** @param in Hessian输入流,用于读取对象数据* @param fieldNames 字段名数组,指定要反序列化的字段顺序* @return 反序列化后的对象* @throws IOException 如果在反序列化过程中发生I/O错误或其他异常*/
public Object readObject(AbstractHessianInput in, String[] fieldNames) throws IOException {try {// 通过Unsafe或其他机制实例化对象,不调用构造函数Object obj = instantiate();// 给对象的所有属性赋值,使用Hessian输入流和字段名数组return readObject(in, obj, fieldNames);} catch (IOException e) {// 如果是I/O异常,直接抛出throw e;} catch (RuntimeException e) {// 如果是运行时异常,直接抛出throw e;} catch (Exception e) {// 如果是其他类型的异常,封装为IOExceptionWrapper并抛出// 这里_type可能是当前反序列化对象的类型,用于提供更详细的错误信息throw new IOExceptionWrapper(_type.getName() + ":" + e.getMessage(), e);}
}
解析:
1、调用了Unsafe的allocationInstance构建了一个对象
protected Object instantiate()throws Exception {return _unsafe.allocateInstance(_type);
}
2、给该对象的所有属性进行赋值,源码如下:
/*** 读取Hessian输入流中的数据,并反序列化到指定的对象实例中,为对象的字段赋值。** @param in Hessian输入流,提供反序列化所需的数据* @param obj 已存在的对象实例,将对其字段进行赋值* @param fieldNames 需要赋值的字段名数组* @return 完成字段赋值后的对象,可能是经过resolve处理的新对象* @throws IOException 如果在反序列化过程中发生I/O错误或其他异常*/
public Object readObject(AbstractHessianInput in, Object obj, String[] fieldNames) throws IOException {try {// 在输入流中添加对当前对象的引用,返回引用IDint ref = in.addRef(obj);// 遍历所有需要赋值的字段for (String fieldName : fieldNames) {// 从字段映射中获取对应字段名的反序列化器FieldDeserializer reader = _fieldMap.get(fieldName);if (reader != null) {// 如果找到了反序列化器,则使用该反序列化器从输入流中读取数据,并为对象的字段赋值reader.deserialize(in, obj);} else {// 如果没有找到对应的反序列化器,则直接从输入流中读取并丢弃该字段的数据// 这可能是为了兼容旧版本的数据格式,或者是处理不需要的字段in.readObject();}}// 对对象进行可能的resolve处理,例如处理代理对象或进行其他后处理Object resolve = resolve(in, obj);// 如果resolve处理返回了新的对象,则在输入流中更新对该对象的引用if (obj != resolve) {in.setRef(ref, resolve);}// 返回完成字段赋值(和可能的resolve处理)后的对象return resolve;} catch (IOException e) {// 如果是I/O异常,直接抛出throw e;} catch (Exception e) {// 如果是其他类型的异常,封装为IOExceptionWrapper并抛出,同时提供发生异常的对象的类型信息throw new IOExceptionWrapper(obj.getClass().getName() + ":" + e, e);}
}
其中FieldDeserializer有很多的子类,不同的类型有不同的反序列化方式,比如字符串属性,赋值的逻辑如下:
void deserialize(AbstractHessianInput in, Object obj)throws IOException {String value = null;try {value = in.readString();_unsafe.putObject(obj, _offset, value);} catch (Exception e) {logDeserializeError(_field, obj, value, e);}}
先是读取一个字符串的值,然后还是调用UnSafe的putObejct方法进行赋值给此对象。
如果属性也是对象类型,那么就递归执行直到所有的属性都为基本数据类型并解析成功为止。
打个号外
本人新搞的个人项目,有意者可到 DDD用户中台 这里购买
可以学习到的体系
-
项目完全从0到1开始架构,包含前端,后端,架构,服务器,技术管理相关运维知识!
- 最佳包名设计,项目分层
-
破冰CRUD,手撕中间件!
-
基于MybatisPlus封装属于自己的DDD ORM框架
-
基于Easyexcel封装属于自己的导入导出组件
-
oss对象存储脚手架(阿里云,minio,腾讯云,七牛云等)
-
邮件脚手架
-
completefuture脚手架
-
redis脚手架
-
xxl-job脚手架
-
短信脚手架
-
常用工具类等
-
-
传统MVC代码架构弊端的解决方案
- DDD+CQRS+ES最难架构
-
结合实际代码的业务场景
-
多租户单点登录中心
-
用户中台
-
消息中心
-
配置中心
-
监控设计
-
-
程序员的职业规划,人生规划
-
打工永远没有出路!
-
打破程序员的35岁魔咒
-
技术带给你的优势和竞争力【启发】
-
万物互联网的淘金之路!
-
技术以外的赚钱路子
可以一起沟通
具体的文章目录
购买链接
DDD用户中台