Easy Netty 系列(六):异常处理详解
异常处理
摘要:异常处理在任何系统中都是重要的组成部分,Netty 的异常处理是通过在 ChannelHandler 中重写 exceptionCaught 方法来实现,这篇文章聚焦于此。
Netty 版本:4.1.70.Final
一、异常处理方式
1、捕获异常处理
public static class InboundHandler extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 处理异常
System.err.println(this.getClass().getSimpleName() + " ---- " + cause.getMessage());
// 向下一个handler传递异常
ctx.fireExceptionCaught(cause);
}
}
P.S. 传递异常将从下个 handler 一直往后传递,不会跳过 OutboundHandler。
2、通过监听 Funture、Promise 处理
1)在调研write
、writeAndFlush
可以获得得到 ChannelFeature,进而可以监听执行结果是否成功、异常。通常在 InboundHandler 中使用。
ctx.executor().schedule(()->{
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
System.out.println("writeAndFlush done.");
if(future.isSuccess()){
System.out.println("send success.");
}else{
// 处理异常
System.out.println("send fail!");
}
}
});
}, 0, TimeUnit.SECONDS);
2)在 OutboundHandler 中,write 方法的入参会带有个Promise
参数,通过 Promise 对象可以处理异常,处理后将通知之前的 handler。使用时通过setSuccess
、setFailure
设置。
三、源码分析
1、是谁触发 exceptionCaught
方法
主要是 AbstractChannelhandlerContext#invokeExceptionCaught 方法。
private void invokeExceptionCaught(final Throwable cause) {
if (invokeHandler()) {
try {
//
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);
}
}
当 channel 触发事件调用 invokeChannelActive、invokeChannelRead 过程中出现异常调用 invokeExceptionCaught。
private void invokeChannelActive() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelInactive(this);
} catch (Throwable t) {
// 处理异常
invokeExceptionCaught(t);
}
} else {
fireChannelInactive();
}
}
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
// 处理异常
invokeExceptionCaught(t);
}
} else {
fireChannelRead(msg);
}
}
2、如果我们创建的 Handler 不处理异常,那么会由 Pipeline 中名为tail
的 ChannelHandlerContext 来捕获异常并打印。
可以看到 DefaultChannelPipeline 中有两个内部类:TailContext、HeadContext,最后的异常就是被 TailContext 的实力tail
处理的,可以看到 onUnhandledInboundException
方法中使用 warn
级别输出了异常信息。
// A special catch-all handler that handles both bytes and messages.
final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {
TailContext(DefaultChannelPipeline pipeline) {
super(pipeline, null, TAIL_NAME, TailContext.class);
setAddComplete();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
onUnhandledInboundUserEventTriggered(evt);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 处理最后的异常,方法如下
onUnhandledInboundException(cause);
}
// 省略...
}
/**
* 处理最后的异常
* Called once a {@link Throwable} hit the end of the {@link ChannelPipeline} without been handled by the user
* in {@link ChannelHandler#exceptionCaught(ChannelHandlerContext, Throwable)}.
*/
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);
}
}
final class HeadContext extends AbstractChannelHandlerContext
implements ChannelOutboundHandler, ChannelInboundHandler {
private final Unsafe unsafe;
HeadContext(DefaultChannelPipeline pipeline) {
super(pipeline, null, HEAD_NAME, HeadContext.class);
unsafe = pipeline.channel().unsafe();
setAddComplete();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.fireExceptionCaught(cause);
}
// 省略...
}
EOF