章节索引 :

Netty 内置编解码器

1. 前言

上节我们讲解了 Netty 的内置编码器以及自定义编码器,本节主要讲解 Netty 提供的几个核心编解码器的抽象类,主要是 MessageToByteEncoderByteToMessageDecoderSimpleChannelInboundHandler

2. 学习目的

Netty 官方也是考虑到了如何减轻开发人员的繁琐、重复性的工作,因此,它内置了一些好用的编解码器抽象,让我们更加便捷的自定义自己想要的编解码器。

通过本节学习,我们需要掌握以下几点

  1. 有哪些编解码器的抽象;
  2. 它们的核心原理是什么。

3. 类关系图

图片描述

4. MessageToByteEncoder

从字面意思上可知,它主要是把消息内容转换成 Byte,也就是说是编码。使用非常的简单,继承 MessageToByteEncoder 可以很容易的开发一个 Handler。

实例:

public class MyEncoder extends MessageToByteEncoder<BaseBean> {
    protected void encode(ChannelHandlerContext channelHandlerContext, 
                          BaseBean baseBean, 
                          ByteBuf byteBuf) throws Exception {
        
        //1.把“数据”转换成字节数组
        byte[] bytes= JSON.toJSONBytes(baseBean);
		//2.把字节数组往ByteBuf容器写
        byteBuf.writeBytes(bytes);
    }
}
ch.pipeline().addLast(new MyEncoder());

源码:保留核心代码

public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter {
    //类型匹配器
    private final TypeParameterMatcher matcher;
    //构造函数
    protected MessageToByteEncoder(boolean preferDirect) {
        //初始化
        this.matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");
        this.preferDirect = preferDirect;
    }
    
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ByteBuf buf = null;
        if (this.acceptOutboundMessage(msg)) {
            //类型判断通过,则处理
            
            I cast = msg;
            //创建ByteBuf
            buf = this.allocateBuffer(ctx, msg, this.preferDirect);
			//调用抽象方法(由子类实现)
            this.encode(ctx, cast, buf);
        } else {
            //类型判断不通过,则往下流转
            ctx.write(msg, promise);
        }
    }
    //抽象方法
    protected abstract void encode(ChannelHandlerContext var1, I var2, ByteBuf var3) throws Exception;
}

5. ByteToMessageDecoder

从字面上我们也很容易猜到它的作用,主要是把 Byte 类型的数据转换成对应实体,也称之为解码。使用非常的简单。

实例:

public class MyDecoder extends ByteToMessageDecoder {
	//把ByteBuf反序列化,并且添加到List里面
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { 
        //1.定义byte[],长度为ByteBuf可读长度
        byte[] bytes=new byte[byteBuf.readableBytes()];
        //2.往byte[]读取数据
        byteBuf.readBytes(bytes);
		//3.对象流
        ByteArrayInputStream is=new ByteArrayInputStream(bytes);
        ObjectInputStream iss=new ObjectInputStream(is);
        User user=(User)iss.readObject();
        //4.关闭流
        is.close();
        iss.close();
		//5.添加到集合
        list.add(user);
    }
}

继承了 ByteToMessageDecoder 这个类之后,我们只需要实现一下 decode () 方法,这里的 in 大家可以看到,传递进来的时候就已经是 ByteBuf 类型,所以我们不再需要强转,第三个参数是 List 类型,我们通过往这个 List 里面添加解码后的结果对象,就可以自动实现结果往下一个 handler 进行传递,我们就实现了解码的逻辑 handler。

6. SimpleChannelInboundHandler

前面讲解 ChannelHandler 多业务情况下的时候,我们讲解到了 SimpleChannelInboundHandler,它的核心作用是自动判断数据格式类型,并且转发给对应的 Handler 来处理。

一般来说,Netty 开发的应用如果很复杂的时候,那么应该如何处理呢?通常有三种方案。

6.1 方案一

对反序列化后的结果进行类型判断,不同的类型做不同的业务处理。

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
	//对反序列化后的结果进行类型判断,不同的类型做不同的业务处理
    if(msg instanceof LoginReqBean){
        login((LoginReqBean) msg,ctx.channel());

    }else if(msg instanceof MsgReqBean){
        sendMsg((MsgReqBean)msg,ctx.channel());
    }
}

这种模式比较简单,但是通过 if else 逻辑进行逻辑的处理,当我们要处理的指令越来越多的时候,代码会显得越来越臃肿。

6.2 方案二

判断是否是自己应该处理,如果不是,则手工往下流转。

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    if(msg instanceof LoginReqBean){
        //业务处理
        
    }else{
        //往下流转
        ctx.fireChannelRead(msg);
    }
}

6.3 方案三

使用 SimpleChannelInboundHandler 来简化我们的指令处理逻辑。

SimpleChannelInboundHandler 使用非常简单,我们在继承这个类的时候,给他传递一个泛型参数,然后在 channelRead0 () 方法里面,我们不用再通过 if 逻辑来判断当前对象是否是本 handler 可以处理的对象,也不用强转,不用往下传递本 handler 处理不了的对象,这一切都已经交给父类 SimpleChannelInboundHandler 来实现了,我们只需要专注于我们要处理的业务逻辑即可。

实例:

public class LoginReqHandler extends SimpleChannelInboundHandler<LoginReqBean> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, LoginReqBean loginReqBean){
        //登录逻辑
    }
}
public class MsgReqHandler extends SimpleChannelInboundHandler<MsgReqBean> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MsgReqBean msgReqBean){
        //消息发送逻辑
    }
}

源码:只保留核心部分

public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter {
    //类型匹配器
    private final TypeParameterMatcher matcher;
    
    protected SimpleChannelInboundHandler(boolean autoRelease) {
        //初始化类型匹配器
        this.matcher = TypeParameterMatcher.find(this, SimpleChannelInboundHandler.class, "I");
    }
    
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (this.acceptInboundMessage(msg)) {
            //类型校验通过通过,则调用抽象方法(子类去实现)
            this.channelRead0(ctx, msg);
        } else {
            //类型校验不通过,则往下流转
            ctx.fireChannelRead(msg);
        }
    }
    
    //抽象方法,由自定义业务类去实现
    protected abstract void channelRead0(ChannelHandlerContext var1, I var2) throws Exception;
}

7. 小结

本节学习主要掌握以下知识点

  1. 基于 MessageToByteEncoder,我们可以实现自定义编码,而不用关心 ByteBuf 的创建,不用每次向写数据的时候,都手工进行编码;
  2. 基于 ByteToMessageDecoder,我们可以实现自定义解码,而不用关心 ByteBuf 的强转和 解码结果的传递;
  3. 基于 SimpleChannelInboundHandler,我们可以实现根据数据格式来判断由哪个 Handler 去处理,不需要手工 if else 判断,不需要手动传递对象,做到了真正关心业务逻辑的处理;
  4. 其实,这三种 Handler 也是有各自的应用场景,ByteToMessageDecoderMessageToByteEncoder 是用来封装解码器和编码器,SimpleChannelInboundHandler 则是用于业务逻辑的简化开发。