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
则是用于业务逻辑的简化开发。