netty实现多数据格式,多对象解析处理

前言

接着上一章讲(netty实现多协议,多编解码器),上文提到String数据和protostuff序列化对象数据使用不同编解码器共同使用,这一章讲讲,多数据格式(多对象)该怎么去解析,当然还是用protostuff序列化对象传输。

具体实现

定义实体类

@Data
public class User implements Serializable {
    
    
    
    private String id;
   
    private String name;
    
    private Integer age;
}
@Data
public class Department implements Serializable {
    
    
    
    private String id;
   
    private String name;
    
    private String code;
}

定义通用类

/**
 * @author: zhouwenjie
 * @description:
 * @create: 2022-07-18 10:21
 **/
@Data
public class MessageData implements Serializable {
    
    

    public enum DataType{
    
    
        user,
        department
    }

    private DataType data_type;

    private User user;

    private Department department;
}

编解码器

编码器

/**
 * @author: zhouwenjie
 * @description:
 * @create: 2022-07-12 11:19
 **/
public class ProtostuffEncoder extends MessageToByteEncoder<MessageData> {
    
    

    private String delimiter;

    public ProtostuffEncoder(String delimiter) {
    
    
        this.delimiter = delimiter;
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, MessageData msg, ByteBuf out) throws Exception {
    
    
        byte[] bytes = ProtostuffUtils.serialize(msg);
        byte[] delimiterBytes = delimiter.getBytes();
        byte[] total = new byte[bytes.length + delimiterBytes.length];
        System.arraycopy(bytes, 0, total, 0, bytes.length);
        System.arraycopy(delimiterBytes, 0, total, bytes.length, delimiterBytes.length);
        out.writeBytes(Unpooled.wrappedBuffer(total));
    }

解码器

public class ProtostuffDecoder<T> extends MessageToMessageDecoder<ByteBuf> {
    
    

    private Class<T> clazz;

    public ProtostuffDecoder(Class<T> clazz) {
    
    
        this.clazz = clazz;
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    
    
        try {
    
    
            byte[] body = new byte[in.readableBytes()];
            in.readBytes(body);
            out.add(ProtostuffUtils.deserialize(body, clazz));
        } catch (Exception e) {
    
    
          e.printStackTrace();
        }
    }
}

集成编解码器
在这里插入图片描述

发送和接收消息

发送消息

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
    
    
        User user= new User();
        user.setName("张三");
        user.setAge(28);
        MessageData messageData = new MessageData();
        messageData.setData_type(MessageData.DataType.User);
        messageData.setComputeResult(user);
        ctx.writeAndFlush(messageData);
    }

接收消息
在这里插入图片描述

	@Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageData messageData) throws Exception {
    
    
       if (messageData.getData_type()== MessageData.DataType.User){
    
    
           System.out.println(messageData.getUser());
       }else {
    
    
           System.out.println(messageData.getDepartment());
       }
    }

扩展

如果不想在一个读方法里处理所有对象数据接收区分的逻辑,那么可以将区分逻辑放在一个自定义的解码器中,然后分别定义user和department的hanlder处理器,这样的也是可以的。

例如下边的,改造一下上边的解码器代码:

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    
    
        try {
    
    
            byte[] body = new byte[in.readableBytes()];
            in.readBytes(body);
            MessageData messageData = (MessageData) ProtostuffUtils.deserialize(body, clazz);
            if (messageData.getData_type()== MessageData.DataType.CarHeartBeat){
    
    
                out.add(messageData.getCarHeartBeat());
            }else {
    
    
                out.add(messageData.getComputeResult());
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

处理逻辑的handler泛型也改一下:
在这里插入图片描述
再加一个Department的handler专门用来处理部门的逻辑即可,这样代码分离的更彻底一点,不过还得看自己需求吧。

终极解决方案(反射)

看了前边的文章和各种方法,相信一定对netty编解码有了更深的理解,针对前边的方法,在只有少量对象格式的情况下,判断也不多,我们可以用,但是一旦对象格式很多,那么判断语句就会大量增加,再看我们的逻辑代码,解码器里只不过是把对象解析出来,然后传递下去而已,这里并没有太多复杂的逻辑,所以,考虑用反射来优化多if判断的代码,这样就兼容性就更好了。

改造解码器

public class ProtostuffDecoder<T> extends MessageToMessageDecoder<ByteBuf> {
    
    

    private Class<T> clazz;

    public ProtostuffDecoder(Class<T> clazz) {
    
    
        this.clazz = clazz;
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    
    
        try {
    
    
            byte[] body = new byte[in.readableBytes()];
            in.readBytes(body);
            MessageData messageData = (MessageData) ProtostuffUtils.deserialize(body, clazz);
            MessageData.DataType data_type = messageData.getData_type();
            String className = data_type.toString();
            //className 一定要是字段的名称
            Field field = messageData.getClass().getDeclaredField(className);
            field.setAccessible(true);
            Object o = field.get(messageData);
            if (o == null) {
    
    
                return;
            }
            out.add(o);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

改造编码器

public class ProtostuffEncoder extends MessageToByteEncoder {
    
    

    private String delimiter;

    public ProtostuffEncoder(String delimiter) {
    
    
        this.delimiter = delimiter;
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
    
    
        MessageData messageData = new MessageData();
        Class<?> aClass = msg.getClass();
        String simpleName = aClass.getSimpleName();
        //设置首字母小写
        simpleName = CommonUtil.lowerFirstCase(simpleName);
        //设置datatype
        messageData.setData_type(MessageData.DataType.valueOf(simpleName));
        Field[] fields = messageData.getClass().getDeclaredFields();
        for (Field field : fields) {
    
    
            Class<?> fieldType = field.getType();
            if (msg.getClass().getName().equals(fieldType.getName())) {
    
    
                field.setAccessible(true);
                field.set(messageData, msg);
                break;
            }
        }
        byte[] bytes = ProtostuffUtils.serialize(messageData);
        byte[] delimiterBytes = delimiter.getBytes();
        byte[] total = new byte[bytes.length + delimiterBytes.length];
        System.arraycopy(bytes, 0, total, 0, bytes.length);
        System.arraycopy(delimiterBytes, 0, total, bytes.length, delimiterBytes.length);
        out.writeBytes(total);
    }
}

这样一来,不管你增加再多的实体类,这块代码都不用再去修改啦!!!
但是一定要记得保证MessageData里DataType的值保持跟下边实体类字段的名字一样。

提示:
经过进一步的项目实践发现,如果你的客户端和服务端都只有一个的情况下,MessageData中可以不要DataType字段,解码判断的时候直接判断字段值是否是空即可,编码器也不用动态去添加类型了,比如下边这样:
在这里插入图片描述
在这里插入图片描述
但是如果是有多个客户端,那么服务端就不能这样做了,因为在解码的时候,因为都是protobuf协议,所以即使不是自己的消息也能解码,虽然解码的内容为空,比如下边这样:
在这里插入图片描述
PlaneMessageData的消息也能被LightMessageData消息的解码器解码。

猜你喜欢

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