纸上得来终觉浅,源码阅读是进一步提高自身水平的手段。但源码无数,并不是什么样的源码都值得一读。
须知任何技术都是为了解决特定问题的,先针对问题进行思考,然后再读源码,会事半功倍。
本文按照一定的阅读源码思路来逐步解析ByteToMessageDecoder源码。
继承关系:
public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter
关键方法:
protected abstract void decode(ChannelHandlerContext var1, ByteBuf var2, List<Object> var3) throws Exception;
使用方式:
public class A extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
// byteBuf中,信息完整且能处理掉的,处理结果放到list中;信息不完整的,不消耗byteBuf,等下次完整了再处理;不能处理的,丢弃掉
}
}
从上述内容入手,可以解析到信息如下:
思考:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
// (略)这里是第二层结构
} else {
ctx.fireChannelRead(msg);
}
}
说明:解析源码的目的不仅仅是在于了解其实现,实际上,在了解问题场景的情况下,一个合格的程序员,花点功夫也能实现。源码往往是精心设计的,不仅是其准确性,其可读性、组织规范、容错性等方面也有很高的参考价值。
解析:其表达的意思很明确,判断传入类型,类型符合则处理,不符合则透传。这是一个组织规范上的问题,在自写处理时,要套用该模式。同时也说明了,设计上可以按照类型去做不同类型的处理,如下:
// 解析多种不同的输入格式。
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
// (略)
} else if (msg instanceof String) {
// (略)
} else {
ctx.fireChannelRead(msg);
}
}
CodecOutputList out = CodecOutputList.newInstance();
boolean var10 = false;
try {
var10 = true;
ByteBuf data = (ByteBuf)msg;
this.first = this.cumulation == null;
if (this.first) {
this.cumulation = data;
} else {
this.cumulation = this.cumulator.cumulate(ctx.alloc(), this.cumulation, data);
}
this.callDecode(ctx, this.cumulation, out);
var10 = false;
} catch (DecoderException var11) {
throw var11;
} catch (Exception var12) {
throw new DecoderException(var12);
} finally {
if (var10) {
if (this.cumulation != null && !this.cumulation.isReadable()) {
this.numReads = 0;
this.cumulation.release();
this.cumulation = null;
} else if (++this.numReads >= this.discardAfterReads) {
this.numReads = 0;
this.discardSomeReadBytes();
}
int size = out.size();
this.decodeWasNull = !out.insertSinceRecycled();
fireChannelRead(ctx, out, size);
out.recycle();
}
}
if (this.cumulation != null && !this.cumulation.isReadable()) {
this.numReads = 0;
this.cumulation.release();
this.cumulation = null;
} else if (++this.numReads >= this.discardAfterReads) {
this.numReads = 0;
this.discardSomeReadBytes();
}
int size = out.size();
this.decodeWasNull = !out.insertSinceRecycled();
fireChannelRead(ctx, out, size);
out.recycle();
解析:在这里,我们找到了用于拼接的cumulation,如下:
ByteBuf data = (ByteBuf)msg;
this.first = this.cumulation == null;
if (this.first) {
this.cumulation = data;
} else {
this.cumulation = this.cumulator.cumulate(ctx.alloc(), this.cumulation, data);
}
解析:并且,这个对象为null时,直接被替换为当前输入的数据,cumulate就是其处理两个buff拼接的代码。
接着看整体代码组织结构
// (略)一些临时变量
try {
var10 = true;
// (略)这个地方会抛异常
var10 = false;
} catch (DecoderException var11) {
throw var11;
} catch (Exception var12) {
throw new DecoderException(var12);
} finally {
if (var10) {
// (略)某处理1
}
}
// (略)某处理2
解析:从这个结构看,并没有什么需要特别关注的,但是,其中的处理1和处理2代码是完全一致的!!我们来思考一下这里为什么要以这种方式组织代码,暗藏何种玄机。
将代码抽象如下:
if (条件1) {
处理1,一旦执行,将不会再满足条件1
} else if (条件2) {
处理2
}
处理3
结合上述流程组织,在不抛异常时,处理1和处理2只能进入其中一个;而在抛异常时,处理1和处理2可能都会执行。这是这个组织结构想表达的意思。
那么,为什么不将其抽取成一个方法进行调用呢?本人理解如下:
代码块
if (this.cumulation != null && !this.cumulation.isReadable()) {
this.numReads = 0;
this.cumulation.release();
this.cumulation = null;
} else if (++this.numReads >= this.discardAfterReads) {
this.numReads = 0;
this.discardSomeReadBytes();
}
int size = out.size();
this.decodeWasNull = !out.insertSinceRecycled();
fireChannelRead(ctx, out, size);
out.recycle();
解析:
public interface Cumulator {
ByteBuf cumulate(ByteBufAllocator var1, ByteBuf var2, ByteBuf var3);
}
public static final ByteToMessageDecoder.Cumulator MERGE_CUMULATOR = new ByteToMessageDecoder.Cumulator() {
public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
ByteBuf buffer;
if (cumulation.writerIndex() <= cumulation.maxCapacity() - in.readableBytes() && cumulation.refCnt() <= 1 && !cumulation.isReadOnly()) {
buffer = cumulation;
} else {
buffer = ByteToMessageDecoder.expandCumulation(alloc, cumulation, in.readableBytes());
}
buffer.writeBytes(in);
in.release();
return buffer;
}
};
解析:
static ByteBuf expandCumulation(ByteBufAllocator alloc, ByteBuf cumulation, int readable) {
ByteBuf oldCumulation = cumulation;
cumulation = alloc.buffer(cumulation.readableBytes() + readable);
cumulation.writeBytes(oldCumulation);
oldCumulation.release();
return cumulation;
}
public static final ByteToMessageDecoder.Cumulator COMPOSITE_CUMULATOR = new ByteToMessageDecoder.Cumulator() {
public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
Object buffer;
if (cumulation.refCnt() > 1) {
buffer = ByteToMessageDecoder.expandCumulation(alloc, cumulation, in.readableBytes());
((ByteBuf)buffer).writeBytes(in);
in.release();
} else {
CompositeByteBuf composite;
if (cumulation instanceof CompositeByteBuf) {
composite = (CompositeByteBuf)cumulation;
} else {
composite = alloc.compositeBuffer(2147483647);
composite.addComponent(true, cumulation);
}
composite.addComponent(true, in);
buffer = composite;
}
return (ByteBuf)buffer;
}
};
解析:
疑问:
public TestDecoder() {
setCumulator(COMPOSITE_CUMULATOR);
}
https://blog.csdn.net/a215095167/article/details/104905170
阅读源码,最主要一点是不要怕。精心设计的源码,可读性是比较强的。如果可读性差,要么是自身还需要历练,学习更多知识,要么是选用的源码没有什么学习价值。因此,阅读源码也是一个自我认识的过程。