如何实现无需声明字段、基于偏移生成getter/setter的自定义注解
方案可行性结论
这个注解方案完全可行,实现原理和Lombok一致,基于Java编译期注解处理器(APT)在编译阶段自动生成getter/setter字节码,没有运行时反射开销,性能和手写代码完全一致,还能避免手动写偏移量、方法名带来的低级错误。
具体实现步骤
1. 定义可重复的@MsgField注解
因为一个消息类对应多个字段,需要支持在类上重复标注@MsgField,同时把你预想中的字符串类型入参改成Class类型,编译期就能校验类型合法性,避免字符串拼错的问题:
import java.lang.annotation.*; // 标注在类上,仅编译期生效 @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) @Repeatable(MsgFields.class) public @interface MsgField { String name(); // 字段名,用于生成get/set方法后缀 Class<?> type(); // 字段数据类型,直接传int.class/short.class等 int index(); // 字段在ByteBuffer中的起始字节偏移 } // 重复注解的容器类,业务代码不需要直接使用 @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface MsgFields { MsgField[] value(); }
定义完之后你写的消息类就可以改成你期望的形式,注意type直接传class即可,比传字符串更可靠:
@MsgField(name="Val1", type=int.class, index=0) @MsgField(name="Val2", type=short.class, index=6) class Message1 { ByteBuffer byteBuffer; public static Message1 wrap(ByteBuffer b) { return new Message1(b); } }
2. 实现编译期注解处理器
注解处理器的核心作用是在javac编译阶段,扫描所有被@MsgField标注的类,按照配置自动生成对应的getter/setter方法插入到类中。
核心逻辑
- 首先注册处理器:可以通过SPI机制在
META-INF/services/javax.annotation.processing.Processor文件中写入处理器的全限定名,也可以引入Google AutoService依赖用@AutoService(Processor.class)注解自动完成注册,省去手动写SPI文件的麻烦。 - 扫描被标注的类,先做基础校验:确认类中存在
ByteBuffer类型的成员变量,避免生成的代码调用不存在的字段报错。 - 遍历类上所有的@MsgField配置,提前建立数据类型和ByteBuffer读写方法的映射关系:
int.class→getInt(index)/putInt(index, 值)short.class→getShort(index)/putShort(index, 值)long.class→getLong(index)/putLong(index, 值)byte.class→get(index)/put(index, 值)float.class→getFloat(index)/putFloat(index, 值)double.class→getDouble(index)/putDouble(index, 值)
- 按照映射生成对应的getter/setter方法,比如针对
@MsgField(name="Val1", type=int.class, index=0),生成的代码和你手写的完全一致:public int getVal1() { return this.byteBuffer.getInt(0); } public void setVal1(int val1) { this.byteBuffer.putInt(0, val1); }
实现方式选择
- 如果你想实现和Lombok完全一致的效果(直接修改原类的字节码,不需要额外类),可以借助javac内部的JCTree API修改编译期的抽象语法树,把生成的方法直接插入到原类中,缺点是不同JDK版本的内部API有差异,需要做版本适配。
- 如果你不想依赖JDK内部API,可以用JavaPoet等代码生成库,生成和原类同包的辅助类,或者让消息类实现一个生成的接口,通过接口调用get/set方法,优点是兼容性好,所有JDK版本都能正常运行。
- 处理器里还可以加编译期校验逻辑:比如根据字段类型占用的字节数(int占4字节、short占2字节),自动检查偏移量是否重叠、是否越界,在编译阶段就抛出错误,比手写代码运行时才发现问题效率高很多。
注意事项
- 网络传输默认是大端字节序,ByteBuffer的默认字节序也是大端,如果你的TCP协议用小端序,记得在wrap方法里给ByteBuffer调用
order(ByteOrder.LITTLE_ENDIAN)修改字节序,否则读出来的数值会出错。 - 如果觉得自己写注解处理器麻烦,也可以直接用现成的二进制序列化工具(比如Protobuf、FlatBuffers)实现类似的偏移量读写字段的能力,但自定义APT的方案最贴合你现有代码的使用习惯,迁移成本最低。
内容的提问来源于stack exchange,提问作者themagicalyang
相关产品推荐
相关产品推荐

