/magic-byte

a java tool for faster convertor byte2object

Primary LanguageJavaBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause

魔法字节(magic-byte)

GitHub (pre-)release GitHub issues GitHub closed issues GitHub

中文|English

1. 简介

在当代物联网行业中,由于隐私和安全问题,很多的公司选择使用自定义的私有二进制协议。 在C语言中,由于有结构体的加持,对象和字节数组转换起来就特别简单;但在java中,在没有原生支持的情况下,开发人员就只能够靠码力去读取解析数据然后转译成为对象 ,流程如下图:

在这看似简单的编码/解码过程中其实会伴随很多人头疼的问题,例如:

  • 大小端/网络字节序的处理
  • 无符号数/有符号数的处理
  • 多字节整数转换处理
  • ASCII码与字节之间的转换处理
  • 空指针/填充数据的处理
  • 数组对象/嵌套对象的处理

此项目的目的便是尽可能的解决上述问题,让大家将更多的时间聚焦在业务中。 在引入MagicByte后,只需要在类定义的同时使用注解声明字段的数据类型。 接下来就只需要调用两个简单的方法即可进行序列化:用于对象转字节的MagicByte.unpack();和用于字节转对象的MagicByte.pack()。 是不是很简单?马上试试吧!

2. 快速入门:

  1. 引入Jar包;
  2. @MagicClass对当前类进行全局配置
  3. @MagicField对需要转换的JAVA对象属性进行标注,支持对象组合嵌套,注意:不支持继承
  4. 使用MagicByte.pack()MagicByte.unpack()对数据或对象进行快速的序列化或反序列化
  5. (可选)支持使用MagicByte.registerCMD注册消息到 MagicByte, 前往查看示例
  6. (可选)支持使用@MagicConverter()注解来实现自定义序列化;前往查看枚举类自定义序列化示例

Maven项目可直接导入: 点击查看版本列表

<dependency>
  <groupId>io.github.misterchangray</groupId>
  <artifactId>magic-byte</artifactId>
  <version>2.4.4</version>
</dependency>

3. 代码示例

以下为简单的框架功能展示,实际项目中数据实体类定义建议参考 数据实体定义的最佳实践

下面的报文示例中, 共有 Student 和 School 两个数据报文:

// declare class must use public
// 使用大端模式, 默认为大端
@MagicClass(byteOrder = ByteOrder.BIG_ENDIAN)
public class School {
    // 10 byte, 普通数据类型, 占用10字节长度
    @MagicField(order = 1, size = 10)
    private String name;
    // 2 byte, 长度字段, 数据序列化时将自动填充实际数据长度
    @MagicField(order = 3, calcLength = true)
    private short length;
    // 支持组合模式, 这里嵌入了 Student 对象
    // 总字节数 = students.bytes * length
    @MagicField(order = 5, size = 2)
    private Student[] students;
    // 0 byte, 注意, 此处无法序列化, 不支持的数据类型将会被忽略
    @MagicField(order = 7)
    private List<Object> notSupport;
    // 0 byte, 注意, 此处无法序列化, 不支持的数据类型将会被忽略
    @MagicField(order = 9)
    private Object age;
    // 4 byte, 注意, 此处指定为秒级时间戳, 同时指定使用4个字节保存, 未指定则默认6个字节
    @MagicField(order = 13, size = 4, timestampFormat = TimestampFormatter.TO_TIMESTAMP_SECONDS)
    private Date[] birthdays;
    // 1 byte, 普通数据类型, 通过order配置序列化顺序, 序列号顺序和定义顺序无关
    @MagicField(order = 15)
    private byte age;
    // 1 byte, 校验和字段, 序列化时如提供计算函数则将会自动填充
    @MagicField(order = 17)
    private byte checkCode;

   
    // getter and setter ...
}

@MagicClass()
public class Student {
    // 10 byte, 普通数据, 长度为 10 字节
    @MagicField(order = 1, size = 10)
    private String name;
    // 4 byte, 普通数据, 整数, 此字段决定后续 phones 字段长度
    @MagicField(order = 5)
    private int length;
    // 总字节数 = phones.size * length
    // 单个元素 8 byte, 此List并未直接指定大小, 大小由 length 字段决定. length字段数据类型只能为 byte, short, int, UNumber
    @MagicField(order = 10, dynamicSizeOf = "length")
    private List<Long> phones;
    // 1 byte
    @MagicField(order = 15)
    private byte age;
    // 生日, 这里为秒级时间戳, 指定使用4个字节, 日期类型未指定则默认6个字节
    @MagicField(order = 18, size = 4, timestampFormat = TimestampFormatter.TO_TIMESTAMP_SECONDS)
    private Date birthDay;
    // getter and setter ...
}

public class Hello {
    void main() {
        // 全局配置校验和计算函数
        MagicByte.configMagicChecker(Checker::customChecker);
        School school = new School();
        school.setAge((byte) 23);
    
        // you can set other propertis
        // object to bytes
        // 也可以单独传入计算函数
        byte[] bytes = MagicByte.unpack(school, Checker::customChecker); 
        School school2 = MagicByte.pack(bytes, School.class); // bytes to object
        System.out.println(school.getAge() == school2.getAge()); // out put true
    
    }
}

public class Checker {
    /**
     * 序列化时: data数据中包含所有已序列化的数据(包括 calcLength 也已经调用并序列化)
     * 反序列化时: data数据为传入数据的副本
     * @param data
     * @return
     */
    public static byte[] customChecker(byte[] data) {
        return new byte[]{0xff};
    }
}

4. 注解和属性说明

工具存在三个注解:

  1. @MagicClass() 类注解; 主要用于数据全局配置
    • byteOrder 配置序列化大小端,可全局配置
    • strict 严格模式, 默认false, 严格模式将会抛出更多的异常
  2. @MagicField() 属性注解, 未注解的属性不参与序列化/反序列化过程
    • order 定义对象属性的序列化顺序(重要, 投入使用后请勿修改, 从1开始递增,建议跳跃配置如:1,3,5...)
    • size 属性大小, 仅UNumber&String&List需要设置, String/UNumber 代表字节长度. List和Array代表成员个数
    • cmdField 标记此字段为消息类型, 此配置结合消息注册使用. 默认false; 消息注册参考
    • charset 字符集设置,可全局配置, 仅String设置有效; 默认ASCII
    • dynamicSize 标记字段为动态长度, 整条消息只能标记一次且仅能标记String&List&Array类型字段; 点击查看详情
    • dynamicSizeOf 从指定的属性中获取List或Array的长度, 仅List,Array,String有效;引用字段类型只能为byte, short, int, UNumber
    • calcLength 标记字段为长度字段, 反序列化时将自动将数据总长度(字节数)填充到此字段; 可能抛出: InvalidLengthException
    • calcCheckCode 标记字段为校验和字段, 序列化或反序列化时将会校验或自动填充; 可能抛出: InvalidCheckCodeException
    • timestampFormat 可指定时间格式,时间戳或者文本,时间戳可指定为毫秒,秒,分钟,小时,天;日期类型默认6字节储存空间,可使用size进行调整;如秒级时间戳4个字节就足够储存传输
    • formatPattern 日期字段使用,如指定序列化为字符串,这里配置序列化格式。默认为:yyyyMMddHHmmss
  3. @MagicConverter()配置自定义序列化,更多说明参考 自定义序列化最佳实践
    • converter, 序列化类, 该类必须为MConverter的子类
    • attachParams, 附加参数;序列化时将会传入
    • fixSize, 固定数据字节长度, 可以统一指定自定义数据的长度,也可忽略然后在序列化时返回实际数据长度

5. 支持的数据类型及字节大小;

数据类型 数据类型 扩展类型(无符号数) 字节大小
byte boolean UByte 1
short char UShort 2
int float UInt 4
long double ULong 8
Date,Instant,DateTime LocalTime,LocalDate,LocalDateTime 6(配合size修改)
String UNumber custom(配合size指定)

6. 注意事项

  1. 因为order属性为对象序列化顺序,所以已投入使用的字段请不要随意修改order属性(重要),这样可能会影响已有业务;新增字段请递增使用新的order
  2. 大端小端使用@MagicClass进行配置
  3. 基本数据类型使用下表默认字节长度, String/List/Array/UNumber 需要使用size属性指定成员长度或字符串字节长度
  4. 请使用基础类型定义报文结构,目前仅支持以下数据类型:
    1. 四类八种基础类型(byte/char/short/int/long/float/double/boolean)
    2. 无符号包装类型(UByte/UShort/Uint/ULong/UNumber) 关于无符号数类型
    3. 支持 String, 但必须申明 Size
    4. 支持 List & Array, 可以使用泛型; 仅支持一维数组且不能使用可变数据类型,如:List<String>String[]List<UNumber>
  5. 数据溢出时工具会自动对数据进行裁剪,如字符串或数组长度声明为5, 则只会序列化集合前5个元素
  6. 字符串默认使用ASCII编码,可局部配置或全局修改
  7. 不支持一维以上的List或者Array
  8. boolean值 0=false/非0=true
  9. 所有类的定义必须为 public, 不支持私有内部类; 支持 public static class XXX {}
  10. 不支持类继承的序列化和反序列化;支持类的嵌套组合使用
  11. 序列化null值,如果是包装数据类型,则使用原始类型默认值;如Short a = null; 序列化为 0; 其他数据类型将会直接填充,如数组,对象等。
  12. 更多问题请前往WIKI页面查看 >> WIKI_HOME
  13. 内置枚举类序列化转换器SimpleEnumConverter,原理是根据枚举定义顺序进行序列化/反序列化.故使用后请不要更改定义顺序。建议自行实现

7. 开发建议

  1. 不建议网络传输浮点数;
  2. 不建议网络传输有符号数即负数
  3. 不建议网络传输字符串
  4. 不建议类中大量声明字符串
  5. 条件允许建议使用protobuf作为序列化框架
  6. 二进制协议建议单包大小在100KB以内
  7. order属性建议跳跃配置(如1,3,5,7), 且对于已经投产使用的字段一定不要随意修改顺序; 新增字段时必须使用递增后的order新值; 重要的事强调3次

8. 最后

本人目前也是做物联网相关领域的(共享充电宝), 开始的时候也是走了不少弯路,这个项目算是在这工作了1年多的工作总结; 写完之后才发现谷歌早在13年前就开源了类似框架, 真是学习使人进步; 又重复造轮子了。 附上谷歌的链接:

9.欢迎大佬入驻交流:

QQ群  562371124