Netty 内置编解码器
1. 前言
上节我们讲解了 Netty 的内置编码器以及自定义编码器,本节主要讲解 Netty 提供的几个核心编解码器的抽象类,主要是 MessageToByteEncoder、ByteToMessageDecoder、SimpleChannelInboundHandler。
2. 学习目的
Netty 官方也是考虑到了如何减轻开发人员的繁琐、重复性的工作,因此,它内置了一些好用的编解码器抽象,让我们更加便捷的自定义自己想要的编解码器。
通过本节学习,我们需要掌握以下几点
- 有哪些编解码器的抽象;
- 它们的核心原理是什么。
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. 小结
本节学习主要掌握以下知识点
- 基于 MessageToByteEncoder,我们可以实现自定义编码,而不用关心 ByteBuf 的创建,不用每次向写数据的时候,都手工进行编码;
- 基于 ByteToMessageDecoder,我们可以实现自定义解码,而不用关心 ByteBuf 的强转和 解码结果的传递;
- 基于 SimpleChannelInboundHandler,我们可以实现根据数据格式来判断由哪个 Handler 去处理,不需要手工
if else判断,不需要手动传递对象,做到了真正关心业务逻辑的处理; - 其实,这三种 Handler 也是有各自的应用场景,
ByteToMessageDecoder和MessageToByteEncoder是用来封装解码器和编码器,SimpleChannelInboundHandler则是用于业务逻辑的简化开发。