我们在使用netty框架的过程中,除了使用内置的处理器外,往往还需要根据自己的设计来实现一些处理器。
通常我们不会直接实现ChannelInboundHandler接口,因为里面涉及到大量方法需要实现,而是继承现有的类,
ChannelInboundHandlerAdapter和SimpleChannelInboundHandler就是我们最常用的两个类,二者实现的功能大致相同,但是在一些细节上还是有差异,需要正确使用,否则会出现一些问题。
ChannelInboundHandlerAdapter是ChannelInboundHandler一个简单实现,默认情况下不会做任何处理。只是简单的将操作通过fire*方法传到ChannelPipeline中的下一个ChannelHandler中,让链中的下一个ChannelHandler去处理,信息经过channelRead方法处理之后不会自动释放(释放了下个处理器还处理啥)。
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.fireChannelRead(msg);
}
我们通常会继承此类,覆写上面的channelRead方法,加入自己的逻辑处理。
需要注意的是,覆写方法后,在结尾一定要加上 ctx.fireChannelRead(msg);,否则消息不会继续传递,这是使用时易犯的错误。
SimpleChannelInboundHandler则是最常用的被继承类,这个类有个好处是支持泛型,不像ChannelInboundHandler,消息用Object传递,需要做类型强制转换。
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (this.acceptInboundMessage(msg)) {
this.channelRead0(ctx, msg);
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (this.autoRelease && release) {
ReferenceCountUtil.release(msg);
}
}
}
protected abstract void channelRead0(ChannelHandlerContext var1, I var2) throws Exception;
我们通常只需要覆写channelRead0抽象方法,注意这个方法命名有点怪,本来在日后版本中更名为messageReceived,但netty5被废弃了,只能先用着这有点别扭的名字了。
SimpleChannelInboundHandler实际是继承自上面ChannelInboundHandlerAdapter了,覆写了channelRead方法,并加入了以下特殊处理 ReferenceCountUtil.release(msg),即自动释放消息。
这就形成了另外一个需要注意的点,即如果使用这个类,并且后续的处理器中仍需要读取消息,则必须手工调用
ReferenceCountUtil.retain(msg),也就是让消息的引用计数加1,否则框架对引用计数为0的消息会执行释放和回收。
这两个类功能非常相似,我们应当根据实际情况选择合适的类来继承,覆写相应方法,并注意避免易犯的问题。
ChannelInboundHandlerAdapter需要覆盖的方法是channelRead,不会自动释放消息,需要调用ctx.fireChannelRead(msg)向后续链条处理器传递消息。
SimpleChannelInboundHandler是ChannelInboundHandlerAdapter的子类,做了额外的处理,会自动释放消息,如果还需要继续传递消息,需调用一次ReferenceCountUtil.retain(msg),同时,需注意,同样也需要调用ctx.fireChannelRead(msg)来触发链条中下一处理器处理。
根据以上不同,ChannelInboundHandlerAdapter通常用于处于链条中间的某些环节处理,对数据进行某些处理,如数据验证,需要将消息继续传递。
SimpleChannelInboundHandler则比较适合链条最后一个环节,该环节处理完后,后续不再需要该消息,因此可以自动释放。
虽然我们可以通过附加操作,改变这两个处理器的默认行为,但与其这么做,不如选择合适的处理器,遵循其处理逻辑更合理。