netty实现pojo对象的编解码(ObjectDecoder、ObjectEncoder详解)

前言

为什么要用它呢?思考一个场景。

场景:
我们的通信内容包含很多不同类型的实体类,比如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);
    }

对象消息处理器,主要接收处理对象消息
在这里插入图片描述

集成自定义的编解码器

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/zwjzone/article/details/125905607
今日推荐