💡 项目三:Java对象传输(Protostuff)
项目概述
项目名称:itstack-demo-netty-2-03
核心功能:使用Protostuff直接传输Java对象,无需定义proto文件
技术要点:Protostuff序列化、自定义编解码器、Schema缓存
为什么需要这个项目?
实际问题:
- Protobuf需要定义.proto文件,开发效率低
- 每次修改数据结构都要重新编译
- 只在Java系统内部通信,不需要跨语言
- 希望直接使用POJO对象,简化开发
解决方案:
- ✅ 无需定义.proto文件,直接使用Java Bean
- ✅ 性能接近Protobuf,开发效率更高
- ✅ 支持复杂的Java对象(嵌套、集合等)
- ✅ 运行时动态生成Schema,灵活方便
适用场景:
- Java微服务之间的RPC通信
- 分布式系统内部数据传输
- 需要传输复杂Java对象的场景
- 快速开发原型系统
核心知识点
1. Protostuff vs Protobuf
| 特性 | Protobuf | Protostuff |
|---|---|---|
| 定义文件 | 需要.proto文件 | 不需要 |
| 编译 | 需要protoc编译 | 不需要 |
| 使用方式 | 生成的Java类 | 直接使用POJO |
| 性能 | 高 | 高(相当) |
| 跨语言 | 支持 | 仅Java |
| 开发效率 | 低 | 高 |
选择建议:
- Java系统内部通信 → Protostuff
- 跨语言通信 → Protobuf
2. 定义POJO对象
MsgInfo.java
public class MsgInfo {
private String channelId;
private String msgContent;
// 必须有无参构造函数!
public MsgInfo() {
}
public MsgInfo(String channelId, String msgContent) {
this.channelId = channelId;
this.msgContent = msgContent;
}
// getter/setter...
}
关键点:
- ✅ 普通的Java Bean
- ✅ 无需继承任何类
- ✅ 无需添加注解
- ⚠️ 必须提供无参构造函数
3. 序列化工具类
SerializationUtil.java
public class SerializationUtil {
// Schema缓存(避免重复创建)
private static Map<Class<?>, Schema<?>> cachedSchema =
new ConcurrentHashMap<>();
// Objenesis用于创建对象实例
private static Objenesis objenesis = new ObjenesisStd();
/**
* 序列化:对象 → 字节数组
*/
public static <T> byte[] serialize(T obj) {
Class<T> cls = (Class<T>) obj.getClass();
LinkedBuffer buffer = LinkedBuffer.allocate(
LinkedBuffer.DEFAULT_BUFFER_SIZE
);
try {
Schema<T> schema = getSchema(cls);
return ProtostuffIOUtil.toByteArray(obj, schema, buffer);
} finally {
buffer.clear(); // 清理缓冲区
}
}
/**
* 反序列化:字节数组 → 对象
*/
public static <T> T deserialize(byte[] data, Class<T> cls) {
T message = objenesis.newInstance(cls); // 创建实例
Schema<T> schema = getSchema(cls);
ProtostuffIOUtil.mergeFrom(data, message, schema);
return message;
}
/**
* 获取Schema(带缓存)
*/
private static <T> Schema<T> getSchema(Class<T> cls) {
Schema<T> schema = (Schema<T>) cachedSchema.get(cls);
if (schema == null) {
schema = RuntimeSchema.createFrom(cls); // 运行时创建
cachedSchema.put(cls, schema);
}
return schema;
}
}
核心概念:
- Schema:描述对象结构的元数据(类似数据库表结构)
- LinkedBuffer:临时缓冲区,使用后需清理
- Objenesis:不调用构造函数创建对象
- cachedSchema:缓存Schema,提升性能
4. 自定义编解码器
ObjEncoder.java
public class ObjEncoder extends MessageToByteEncoder {
private Class<?> genericClass;
public ObjEncoder(Class<?> genericClass) {
this.genericClass = genericClass;
}
@Override
protected void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) {
if (genericClass.isInstance(in)) {
byte[] data = SerializationUtil.serialize(in);
out.writeInt(data.length); // 写入长度(4字节)
out.writeBytes(data); // 写入数据
}
}
}
ObjDecoder.java
public class ObjDecoder extends ByteToMessageDecoder {
private Class<?> genericClass;
public ObjDecoder(Class<?> genericClass) {
this.genericClass = genericClass;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in,
List<Object> out) {
// 1. 检查是否有足够的字节读取长度字段
if (in.readableBytes() < 4) {
return; // 等待更多数据
}
// 2. 标记当前读位置
in.markReaderIndex();
// 3. 读取数据长度
int dataLength = in.readInt();
// 4. 检查是否有足够的数据
if (in.readableBytes() < dataLength) {
in.resetReaderIndex(); // 重置读位置
return; // 等待更多数据
}
// 5. 读取数据并反序列化
byte[] data = new byte[dataLength];
in.readBytes(data);
out.add(SerializationUtil.deserialize(data, genericClass));
}
}
解决粘包/半包的关键:
markReaderIndex():标记读位置resetReaderIndex():重置到标记位置- 长度字段前缀:
[4字节长度][N字节数据]
5. Pipeline配置
MyChannelInitializer.java
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) {
// 对象传输处理
channel.pipeline().addLast(new ObjDecoder(MsgInfo.class));
channel.pipeline().addLast(new ObjEncoder(MsgInfo.class));
channel.pipeline().addLast(new MyServerHandler());
}
}
项目总结
优点:
- ✅ 无需定义.proto文件
- ✅ 直接使用POJO对象
- ✅ 开发效率高
- ✅ 性能接近Protobuf
缺点:
- ❌ 仅支持Java
- ❌ 需要提供无参构造函数
适用场景:
- Java微服务之间的RPC通信
- 分布式系统内部数据传输
- 需要传输复杂Java对象的场景
5186

被折叠的 条评论
为什么被折叠?



