天天看點

netty實作pojo對象的編解碼(ObjectDecoder、ObjectEncoder詳解)前言Object編解碼器分析自定義Object解碼器項目內建

對象編解碼器詳解

  • 前言
  • Object編解碼器分析
  • 自定義Object解碼器
  • 項目內建
    • 內建原始的編解碼器
    • 內建自定義的編解碼器

前言

為什麼要用它呢?思考一個場景。

場景:

我們的通信内容包含很多不同類型的實體類,比如user、role、goods、order等等pojo對象需要傳遞,那麼在我們handler的處理器channelRead0方法中,要怎麼才能快速知道這個資料是什麼對象呢,如果用String或者bytebuf等等,那麼要知道這是什麼對象,需要進行很多的邏輯處理,是以Object編碼器就應運而生了,它可以幫我們直接在channelRead0方法中拿到資料類型,無需做其他的解析處理。

在內建之前,先來看一下bject編解碼器怎麼用。

Object編解碼器分析

首先簡單的看一下源碼,大緻了解一下編解碼器的原理。

netty實作pojo對象的編解碼(ObjectDecoder、ObjectEncoder詳解)前言Object編解碼器分析自定義Object解碼器項目內建

标紅的地方可以看出來,使用Object的編解碼器,首先我們要

傳輸的資料需要實作Serializable接口

,其次,也可以看出來,這個編碼器在傳輸的時候在傳輸資料其實位置加了4個位元組,再看一下解碼器:

netty實作pojo對象的編解碼(ObjectDecoder、ObjectEncoder詳解)前言Object編解碼器分析自定義Object解碼器項目內建

解碼器繼承了LengthFieldBasedFrameDecoder,解析的時候去掉了編碼器加的這四個位元組,那麼就說明

Object編解碼器是自帶防止拆包、粘包功能的

,是以,如無特殊需求,不用再去另外實作一套拆包、粘包的方法了。

另外,值得注意的地方是,Object編解碼器能夠将對象轉成流傳輸,然後又能将流轉成對象,這個原理是因為在編碼的時候,它

将對象的類名也傳輸了過去

,是以才能做到精準解碼成對應的對象,舉個簡單的例子說明:

netty實作pojo對象的編解碼(ObjectDecoder、ObjectEncoder詳解)前言Object編解碼器分析自定義Object解碼器項目內建

可以看到,在

用戶端

項目

com.hwdz.compute.entity

包路徑下,有一個BerthGuide實體類,那麼在編碼的時候會将實體類BerthGuide的類名

com.hwdz.compute.entity.BerthGuide

通過反射的方式拿到,并且封裝起來,一塊傳遞到服務端,服務端在解碼的時候,會拿到類名,然後再将資料流轉換成對應的實體類BerthGuide。

總結,為了能夠讓對象在轉流傳輸之後,還能解析還原成對應的對象,我們

必須保證用戶端和服務端的對象所在包名路徑一緻

netty實作pojo對象的編解碼(ObjectDecoder、ObjectEncoder詳解)前言Object編解碼器分析自定義Object解碼器項目內建

但是如果因為特殊需求我們不能保證項目的包名路徑一緻的話,那隻能自定義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也是可以的

netty實作pojo對象的編解碼(ObjectDecoder、ObjectEncoder詳解)前言Object編解碼器分析自定義Object解碼器項目內建

項目內建

內建原始的編解碼器

@Override
    protected void initChannel(SocketChannel ch) {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
        pipeline.addLast(new ObjectEncoder());
        pipeline.addLast(lightsHandler);
    }
           

對象消息處理器,主要接收處理對象消息

netty實作pojo對象的編解碼(ObjectDecoder、ObjectEncoder詳解)前言Object編解碼器分析自定義Object解碼器項目內建

內建自定義的編解碼器

netty實作pojo對象的編解碼(ObjectDecoder、ObjectEncoder詳解)前言Object編解碼器分析自定義Object解碼器項目內建
netty實作pojo對象的編解碼(ObjectDecoder、ObjectEncoder詳解)前言Object編解碼器分析自定義Object解碼器項目內建