-
Notifications
You must be signed in to change notification settings - Fork 20
自定义序列化的最佳实践
ray.zh edited this page Jun 6, 2024
·
15 revisions
使用@MagicConverter
注解对需要自定义序列化的类或成员属性进行标记,即可以在类或类属性上使用该注解。
该注解需要配置一个转换器类,转换器类需要实现MConverter
接口。
接下来,框架在进行序列化/反序列化时将会使用配置的转换器进行装包和拆包。
该注解有三个成员属性:
-
converter
,该属性用于传入转换器类 -
attachParams
, 该属性为自定义参数,框架在调用MConverter
中的方法时会携带该参数 -
fixSize
, 自定义序列化数据的字节长度,可不设置此处,然后在调用序列化方法时返回对应长度 -
handleCollection
,是否自定义序列化整个集合。true时需要控制整个集合的序列化过程,false则只需要控制单个元素的序列化过程
/**
* 自定义一个枚举转换器
*/
public class CustomSexEnumSerialize implements MConverter<SexEnum> {
// 定义为数组, 固定枚举顺序
private SexEnum[] sexEnums = new SexEnum[] {SexEnum.MAN, SexEnum.WOMAN, SexEnum.UNKNOWN};
/**
* 封包,将字节数据转为对象
* @param nextReadIndex 起始位置,即在此之前的都已读取
* @param fullBytes 完整字节数据
* @param attachParams 附加参数
* @param clz 正在pack操作的类
* @param fieldObj正在pack操作的对象
* @param rootObj正在pack操作的根对象, rootObj 和 fieldObj 在进行嵌套结构pack操作时将会不同
* @return 返回总共读取的字节数和封装好的对象
*/
@Override
public MResult<SexEnum> pack(int nextReadIndex, byte[] fullBytes, String attachParams, Class clz, Object fieldObj, Object rootObj) {
byte index = fullBytes[4];
// 注意,如果没有在注解中申明`fixSize`, 这里必须要返回读取的字节数;
// 框架序列化接下来的属性时将会跳过这些字节
return MResult.build(
1,
sexEnums[index]
);
}
/**
* 拆包,将对象拆包为字节数组
* @param sexEnum
* @param attachParams 附加参数
* @param rootObj正在unpack操作的根对象
* @return
*/
@Override
public byte[] unpack(SexEnum sexEnum, String attachParams, Object rootObj) {
for (int i = 0; i < sexEnums.length; i++) {
if(sexEnum == sexEnums[i]) {
return new byte[]{(byte)i};
}
}
return null;
}
}
/**
* 实体类中即可使用上面定义的转换器
*/
@MagicClass()
public class CustomConverterInField {
@MagicField(order = 1, size = 5)
private String name;
@MagicField(order = 2)
@MagicConverter(converter = CustomSexEnumSerialize.class)
private SexEnum sex;
@MagicField(order = 3)
private int age;
}
这个例子自定义序列化一个自定义类
/**
* 自定义一个转换器
*/
public class CustomBookConverter implements MConverter<Book> {
@Override
public MResult<Book> pack(int nextReadIndex, byte[] fullBytes, String attachParams, Class clz, Object fieldObj, Object rootObj) {
if(attachParams.equals("1")) {
// ignore
} else if(attachParams.equals("2")) {
// ignore
}else if(attachParams.equals("3")) {
// 注意 attachParams参数
Book2 book2 = new Book2();
book2.setCreateDate(new Date());
book2.setId(24);
// 注意返回值中需要返回读取的字节数
return MResult.build(0,book2);
}
return MResult.build(0, null);
}
@Override
public byte[] unpack(Book object, String attachParams, Object rootObj) {
// 序列化过程, 省略了
return new byte[0];
}
}
/**
* 定义实体类1
*/
@MagicClass
public class Book2 {
private int id;
private Date createDate;
// ignore getter/stter....
}
/**
* 定义实体类2
* 这里对于 Book2,我们使用自定义的转换器来执行
*/
@MagicClass
public class Staff10 {
@MagicField(order = 1)
private int id;
// 注意这里 attachParams 参数
@MagicConverter(converter = CustomBookConverter.class, attachParams = "3")
@MagicField(order = 3)
private Book2 book;
@MagicField(order = 4, size = 4)
private String name;
// ignore getter/stter....
}
有些时候,数据报文设计时可能存在一个动态的类型属性,该属性决定了接来下的数据解析方式(即该属性决定了接下来序列化的java类)。
这种情况下也可以直接使用自定义序列化,将成员类型直接设置为Object即可。
例如还是上面的示例(修改了attachParam
和成员类型为Object
):
/**
* 自定义一个转换器
*/
public class CustomBookConverter implements MConverter<Object > {
@Override
public MResult<Object > pack(int nextReadIndex, byte[] fullBytes, String attachParams, Class clz, Object fieldObj, Object rootObj) {
if(attachParams.equals("1")) {
// ignore
} else if(attachParams.equals("2")) {
// ignore
} else if(attachParams.equals("3")) {
// ignore
} else if(attachParams.equals("4")) {
// 注意 attachParams参数修改为4, 而且这里可以返回任意对象
Map res = new HashMap();
// 注意返回值中需要返回读取的字节数
return MResult.build(0,res );
}
return MResult.build(0, null);
}
@Override
public byte[] unpack(Object object, String attachParams, Object rootObj) {
// 序列化过程中, 需要判断object类型再进行序列化; 这里省略了
return new byte[0];
}
}
/**
* 定义实体类1
*/
@MagicClass
public class Book2 {
private int id;
private Date createDate;
// ignore getter/stter....
}
/**
* 定义实体类2
* 这里对于 Book2,我们使用自定义的转换器来执行
*/
@MagicClass
public class Staff10 {
@MagicField(order = 1)
private int id;
// 注意这里 attachParams 参数修改为 4 了,上一个示例为 3; 另外这个属性对象为Object,这样在自定义解析中就可以返回任意对象了
@MagicConverter(converter = CustomBookConverter.class, attachParams = "4")
@MagicField(order = 3)
private Object book;
@MagicField(order = 4, size = 4)
private String name;
// ignore getter/stter....
}
框架也支持控制集合类的序列化,但有两种方式。
第一种也就是handleCollection=false
(默认), 此方式下开发者只需要实现集合类中的单个元素序列化流程即可(默认),框架根据集合大小来循环调用从而完成整个序列化流程。
第二种handleCollection=true
, 此种方式需要需要开发者控制整个集合的序列化流程,因为没有集合大小的配置,所以集合边界,长度都由开发者来控制。
自定义序列化虽然灵活,但需要注意以下几点:
- 序列化或者反序列化时使用字节数一定要一致,因为框架需要这些字节数来确定下一个字段数据读取的位置 举个例子,一个对象在自定义序列化时序列化了5个字节,而在反序列化时只读了3个字节,那么读取下一个属性的数据时将会从第4个字节处开始读取。
- 推荐使用
fixSize
属性标明自定义序列化对象的字节数,没有数据时框架自动置零。此处需要注意:框架不能处理null
的情况,此种情况反序列化后将是一个空实例。 - 未使用
fixSize
属性,则必须在反序列化时返回已处理的字节数。 - 注解标记后,该类将会全局使用申明的转换器进行转换,即使是基础类型例如
String, Integer
之类的。 - 一定要保证序列化/反序列化的字节数一致,这是最重要的。
- array或者list序列化时,每次处理的应是单个对象的数据。框架是根据list.size调用相应次数的序列化方法来完成数据的处理
- 在
com.github.misterchangray.core.customconverter.CustomConverterTest
中有很多测试用例可以参考
若大家在使用过程中有疑问,可以参考右边的目录列表进行查询