2021SC@SDUSC
异常的传播
在Netty中如果发生了异常,那么异常事件的传播和当前的节点是 入站处理器还是出站处理器是无关的,异常会一直往下一个节点传播,如果一直没有handler处理异常,最后会由tail节点处理
为了说明这一点,看一个例子:
public class InBoundHandlerA extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
System.out.println("InBoundHandlerA.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception{
throw new Exception("from inboundHandlerB");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
System.out.println("InBoundHandlerB.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
public class InBoundHandlerC extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
System.out.println("InBoundHandlerC.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
public class OutBoundHandlerA extends ChannelOutboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
System.out.println("OutBoundHandlerA.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
public class OutBoundHandlerB extends ChannelOutboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
System.out.println("OutBoundHandlerB.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
public class OutBoundHandlerC extends ChannelOutboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
System.out.println("OutBoundHandlerC.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
创建三个InBoundHandler和三个OutBoundHandler,并且都重写了exceptionCaught()方法,这个方法在异常传播到当前节点的时候被调用。另外,inBoundB还重写了channelRead()方法,当有read事件进来的时候会抛出一个异常来模拟异常事件的发生。
启动类如下:
public class MyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ChannelInboundHandler1());
pipeline.addLast(new ChannelInboundHandler2()); // 异常抛出
pipeline.addLast(new OutBoundHandlerA());
pipeline.addLast(new OutBoundHandlerB());
pipeline.addLast(new OutBoundHandlerC());
}
});
ChannelFuture future = bootstrap.bind(8888).sync();
System.out.println("服务器已启动");
future.channel().closeFuture().sync();
} finally {
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
}
}
启动服务端后,向服务端发送消息,出现异常后,打印情况如下:
InBoundHandlerB.exceptionCaught()
InBoundHandlerC.exceptionCaught()
OutBoundHandlerA.exceptionCaught()
OutBoundHandlerB.exceptionCaught()
OutBoundHandlerC.exceptionCaught()
可以看到,异常在传递的时候是不分Inbound、Outbound,而是从发生异常的节点往后传递
下面来看一下异常是怎么往后传递的:
从抛出异常的地方InBoundHandlerB.channelRead() 开始分析:
public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception{
throw new Exception("from inboundHandlerB");
}
}
抛出异常后,被捕捉的地方在AbstractChannelHandlerContext类的invokeChannelRead()方法里:
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRead(msg);
}
}
当前节点执行channelRead方法时抛异常了,所以跳到了catch部分,执行了notifyHandlerException ()方法:
private void notifyHandlerException(Throwable cause) {
if (inExceptionCaught(cause)) {
if (logger.isWarnEnabled()) {
logger.warn(
"An exception was thrown by a user handler " +
"while handling an exceptionCaught event", cause);
}
return;
}
invokeExceptionCaught(cause);
}
最后一句 invokeExceptionCaught(cause); 调用当前发生异常的节点处理器的exceptionCaught()(当前节点是B):
private void invokeExceptionCaught(final Throwable cause) {
if (invokeHandler()) {
try {
//handler()返回当前节点
handler().exceptionCaught(this, cause);
} catch (Throwable error) {
if (logger.isDebugEnabled()) {
logger.debug(
"An exception {}" +
"was thrown by a user handler's exceptionCaught() " +
"method while handling the following exception:",
ThrowableUtil.stackTraceToString(error), cause);
} else if (logger.isWarnEnabled()) {
logger.warn(
"An exception '{}' [enable DEBUG level for full stacktrace] " +
"was thrown by a user handler's exceptionCaught() " +
"method while handling the following exception:", error, cause);
}
}
} else {
fireExceptionCaught(cause);
}
}
}
当前节点的exceptionCaught()就是上面我们自定义的方法:
public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
System.out.println("InBoundHandlerB.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
这个方法执行了 ctx.fireExceptionCaught(cause); 这个方法是AbstractChannelHandlerContext类中的一个方法:
public ChannelHandlerContext fireExceptionCaught(final Throwable cause) {
// 此时会查询出ChannelInboundHandler3节点
// 调用指定节点的exceptionCaught()
invokeExceptionCaught(findContextInbound(MASK_EXCEPTION_CAUGHT), cause);
return this;
}
findContextInbound是从当前节点的后面进行查询,查询到节点C,然后invokeExceptionCaught()调用指定节点(也就是节点C)的exceptionCaught():
static void invokeExceptionCaught(final AbstractChannelHandlerContext next, final Throwable cause) {
ObjectUtil.checkNotNull(cause, "cause");
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeExceptionCaught(cause);
} else {
try {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeExceptionCaught(cause);
}
});
} catch (Throwable t) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to submit an exceptionCaught() event.", t);
logger.warn("The exceptionCaught() event that was failed to submit was:", cause);
}
}
}
}
}
这里跟节点B的逻辑是一样的,按照这个逻辑,异常事件以inBoundHandlerB-> inBoundHandlerC-> outBoundHandlerA-> outBoundHandlerB-> outBoundHandlerC->tail的顺序进行传播,即根据pipeline中ChannelHandler的顺序从当前节点向后依次传递事件。当异常传播到tail节点后,会执行到tail节点的exceptionCaught:
final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
onUnhandledInboundException(cause);
}
protected void onUnhandledInboundException(Throwable cause) {
try {
logger.warn(
"An exceptionCaught() event was fired, and it reached at the tail of the pipeline. " +
"It usually means the last handler in the pipeline did not handle the exception.",
cause);
} finally {
ReferenceCountUtil.release(cause);
}
}
}
logger.warn()会打印出一个警告信息:异常传递到pipline尾部,通常意味着最后一个处理器都没有处理这个异常,然后ReferenceCountUtil.release(cause);释放异常
当然,我们可以定义一个专门的异常处理器,放到pipline链的最后(也就是tail之前):
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//在pipeline的最后添加异常处理handler
channelPipeline.addLast(new ExceptionCaughtHandler());
}
}
最终全部的异常都会来到这里:
public class ExceptionCaughtHandler extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (cause instanceof 自定义异常1){
}else if(cause instanceof 自定义异常2){
}
// 不再向下传递了
// ctx.fireExceptionCaught(cause);
}
}