對象編解碼器詳解
- 前言
- Object編解碼器分析
- 自定義Object解碼器
- 項目內建
-
- 內建原始的編解碼器
- 內建自定義的編解碼器
前言
為什麼要用它呢?思考一個場景。
場景:
我們的通信内容包含很多不同類型的實體類,比如user、role、goods、order等等pojo對象需要傳遞,那麼在我們handler的處理器channelRead0方法中,要怎麼才能快速知道這個資料是什麼對象呢,如果用String或者bytebuf等等,那麼要知道這是什麼對象,需要進行很多的邏輯處理,是以Object編碼器就應運而生了,它可以幫我們直接在channelRead0方法中拿到資料類型,無需做其他的解析處理。
在內建之前,先來看一下bject編解碼器怎麼用。
Object編解碼器分析
首先簡單的看一下源碼,大緻了解一下編解碼器的原理。
标紅的地方可以看出來,使用Object的編解碼器,首先我們要
傳輸的資料需要實作Serializable接口
,其次,也可以看出來,這個編碼器在傳輸的時候在傳輸資料其實位置加了4個位元組,再看一下解碼器:
解碼器繼承了LengthFieldBasedFrameDecoder,解析的時候去掉了編碼器加的這四個位元組,那麼就說明
Object編解碼器是自帶防止拆包、粘包功能的
,是以,如無特殊需求,不用再去另外實作一套拆包、粘包的方法了。
另外,值得注意的地方是,Object編解碼器能夠将對象轉成流傳輸,然後又能将流轉成對象,這個原理是因為在編碼的時候,它
将對象的類名也傳輸了過去
,是以才能做到精準解碼成對應的對象,舉個簡單的例子說明:
可以看到,在
用戶端
項目
com.hwdz.compute.entity
包路徑下,有一個BerthGuide實體類,那麼在編碼的時候會将實體類BerthGuide的類名
com.hwdz.compute.entity.BerthGuide
通過反射的方式拿到,并且封裝起來,一塊傳遞到服務端,服務端在解碼的時候,會拿到類名,然後再将資料流轉換成對應的實體類BerthGuide。
總結,為了能夠讓對象在轉流傳輸之後,還能解析還原成對應的對象,我們
必須保證用戶端和服務端的對象所在包名路徑一緻
。
但是如果因為特殊需求我們不能保證項目的包名路徑一緻的話,那隻能自定義Object解碼器了,但是最少要保證明體類名稱一緻。
自定義Object解碼器
根據現有的解碼器實作原理,照葫蘆畫瓢,自定義一個差不多的解碼器即可,具體實作代碼如下:
/**
* @author: zhouwenjie
* @description:
* @create: 2022-07-21 11:19
**/
public class MyObjectDecoder extends LengthFieldBasedFrameDecoder {
private String classPath;
public MyObjectDecoder(String classPath) {
this(1048576, classPath);
}
public MyObjectDecoder(int maxObjectSize, String classPath) {
super(maxObjectSize, 0, 4, 0, 4);
this.classPath = classPath;
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ObjectInputStream ois = null;
ByteBuf byteBuf = in.retainedDuplicate();
try {
ByteBuf frame = (ByteBuf) super.decode(ctx, in);
if (frame == null) {
//不能這麼寫,否則會報錯資料太長的錯誤
//in.resetReaderIndex();
//ByteBuf byteBuf = in.retainedDuplicate();
//in.skipBytes(in.readableBytes());
return byteBuf;
}
ois = new MyCompactObjectInputStream(new ByteBufInputStream(frame, true), classPath);
return ois.readObject();
} catch (Exception e) {
//in.resetReaderIndex();
//ByteBuf byteBuf = in.retainedDuplicate();
//in.skipBytes(in.readableBytes());
return byteBuf;
} finally {
if (ois != null) {
ois.close();
}
}
}
}
/**
* @author: zhouwenjie
* @description:
* @create: 2022-07-22 16:43
**/
public class MyCompactObjectInputStream extends ObjectInputStream {
private String classPath;
public MyCompactObjectInputStream(InputStream in, String classPath) throws IOException {
super(in);
this.classPath = classPath;
}
@Override
protected void readStreamHeader() throws IOException {
int version = readByte() & 0xFF;
if (version != STREAM_VERSION) {
throw new StreamCorruptedException(
"Unsupported version: " + version);
}
}
@Override
protected ObjectStreamClass readClassDescriptor()
throws IOException, ClassNotFoundException {
int type = read();
if (type < 0) {
throw new EOFException();
}
switch (type) {
case 0:
return super.readClassDescriptor();
case 1:
String className = readUTF();
//com.hwdz.netty.entity.BerthGuide
if (!classPath.equals(className)) {
String[] split = className.split("\\.");
String s = split[split.length - 1];
className = classPath + s;
}
Class<?> clazz = Class.forName(className);
return ObjectStreamClass.lookupAny(clazz);
default:
throw new StreamCorruptedException(
"Unexpected class descriptor type: " + type);
}
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Class<?> clazz;
try {
clazz = Class.forName(desc.getName());
} catch (ClassNotFoundException ignored) {
clazz = super.resolveClass(desc);
}
return clazz;
}
}
實作自動轉換的邏輯就是:首先擷取本項目pojo所在的包路徑,然後在擷取傳遞過來的包路徑邏輯中,直接替換掉實體類的路徑,這樣就轉成實體類對象在本項目下的路徑了。
當然,如果閑麻煩可以直接繼承現成的ObjectDecoder也是可以的
項目內建
內建原始的編解碼器
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
pipeline.addLast(new ObjectEncoder());
pipeline.addLast(lightsHandler);
}
對象消息處理器,主要接收處理對象消息