Netty分布式pipeline管道异常传播事件源码解析
作者:向南是个万人迷 发布时间:2021-08-15 16:12:02
讲完了inbound事件和outbound事件的传输流程, 这一小节剖析异常事件的传输流程
传播异常事件
简单的异常处理的场景
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
throw new Exception("throw Exception");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println(cause.getMessage());
}
我们在handler的channelRead方法中主动抛出异常, 模拟程序中出现异常的场景, 经测试会发现, 程序最终会走到exceptionCaught方法中, 获取异常对象并打印其信息
那么抛出异常之后, 是如何走到exceptionCaught方法的呢?
我们回顾之前小节channelRead事件的传播流程, channelRead方法是在AbstractChannelHandlerContext类的invokeChannelRead方法中被调用
我们跟到invokeChannelRead这个方法
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
//调用了当前handler的channelRead方法, 其实就是head对象调用自身的channelRead方法
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
//发生异常的时候在这里捕获异常
notifyHandlerException(t);
}
} else {
fireChannelRead(msg);
}
}
这里不难看出, 当调用户自定义的handler的channelRead方法发生异常之后, 会被捕获, 并调用notifyHandlerException方法, 并传入异常对象, 也就是我们示例中抛出的异常
我们跟到fireChannelRead方法中:
private void notifyHandlerException(Throwable cause) {
//代码省略
invokeExceptionCaught(cause);
}
再继续跟到invokeExceptionCaught方法中:
private void invokeExceptionCaught(final Throwable cause) {
if (invokeHandler()) {
try {
//当前handler调用exceptionCaught()方法
handler().exceptionCaught(this, cause);
} catch (Throwable error) {
//代码省略
}
} else {
fireExceptionCaught(cause);
}
}
走到这里一切都明白了, 这里调用了当前handler的exceptionCaught方法, 也就是我们重写的exceptionCaught方法
知道了为什么会走到exceptionCaught方法之后, 我们再进行剖析异常事件的传播流程
我还是通过两种写法来进行剖析
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println(cause.getMessage());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//写法1
ctx.fireChannelRead(cause);
//写法2
ctx.pipeline().fireExceptionCaught(cause);
}
这两种写法我们并不陌生, 可能我们能直接猜到, 第一种写法是从当前节点进行传播, 第二种写法则从头结点或者尾节点进行转播, 那么和传播inbound事件或outbound事件有什么区别呢?我们先以第二种写法为例, 剖析异常事件传输的整个流程
跟到DefualtChannelPipeline的fireExceptionCaught方法中:
public final ChannelPipeline fireExceptionCaught(Throwable cause) {
AbstractChannelHandlerContext.invokeExceptionCaught(head, cause);
return this;
}
我们看到invokeExceptionCaught传入了head节点, 我们可以猜测, 异常事件的传播是从head节点开始的
跟进invokeExceptionCaught方法
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) {
//忽略代码
}
}
}
因为这里是传入的是head节点, 所以这里的next指向head节点
我们跟到invokeExceptionCaught方法中, 这里其实是headContext的父类AbstractChannelHandlerContext中的方法:
private void invokeExceptionCaught(final Throwable cause) {
if (invokeHandler()) {
try {
//当前handler调用exceptionCaught()方法
handler().exceptionCaught(this, cause);
} catch (Throwable error) {
//代码省略
}
} else {
fireExceptionCaught(cause);
}
}
这里又是我们熟悉的逻辑, 调用当前handler的exceptionCaught方法, 因为当前handler是head, 所以首先会调用headContext的exceptionCaught方法
跟进exceptionCaught方法:
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.fireExceptionCaught(cause);
}
这里仅仅是继续传播异常事件, 这时候我们发现, 这个写法和我们刚才提到传播异常事件的两种写法的第一种写法一样:
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//写法1
ctx.fireChannelRead(cause);
//写法2
ctx.pipeline().fireExceptionCaught(cause);
}
根据我们之前的学习, 我们知道第一种写法是从当前节点传播, 而第二种写法是从头传播, 并且要求传播事件一定要使用第一种写法, 否则事件到这里会重新从头传播进而引发不可预知错误, 这个结论在异常传播同样适用, 同学们一定要注意这点
我们继续跟fireExceptionCaught方法, 这里会走到AbstractChannelHandlerContex类的fireExceptionCaught方法:
public ChannelHandlerContext fireExceptionCaught(final Throwable cause) {
//传播异常事件的时候, 直接拿了当前节点的下一个节点
invokeExceptionCaught(next, cause);
return this;
}
这个时候我们发现, 这里并没有去获取下一个的inbound节点还是outbound节点, 而是直接通过next拿到下一个节点, 这就说明在异常事件传播的过程中是不区分inbound事件还是outbound事件的, 都是直接从head节点按照链表结构往下传播,
跟到invokeExceptionCaught方法中
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) {
//代码省略
}
}
}
这里又是我们熟悉的逻辑, 我们知道invokeExceptionCaught中执行了next的exceptionCaught, 这里的next, 因为我们是从head节点开始剖析的, 所以这里很有可能就是用户自定义的handler, 如果用户没有重写exceptionCaught方法, 则会交给用户handler的父类处理
我们以ChannelInboundHandlerAdapter为例看它的该方法实现:
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.fireExceptionCaught(cause);
}
我们看到这里继续向下传播了异常事件
走到这里我们会知道, 如果我们没有重写exceptionCaught方法, 异常事件会一直传播到链表的底部, 就是tail节点
我们跟到TailConext的exceptionCaught方法:
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
onUnhandledInboundException(cause);
}
我们看到最终这里释放了异常对象
来源:https://www.cnblogs.com/xiangnan6122/p/10204484.html


猜你喜欢
- 一、注解的概念1、注解官方解释注解叫元数据,一种代码级别的说明,它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举在同一个层次,它可
- 先看看下面的代码能不能编译通过:public static void main(String[] args) {List l1 = new
- 在写程序的时候,有时候可能需要设置小数的位数,那么java中有哪几种保留小数位数的方法呢?本文以两位小数为例给出四种方法。package C
- 最近在项目中用到了上下滚动展示条目内容,就使用kotlin简单编写实现了一下该功能。使用kotlin实现viewflipper展示textv
- Springboot Aspect切面的实现今天我们来说说spring中的切面Aspect,这是Spring的一大优势。面向切面编程往往让我
- 引言:前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听到“事件”这个概念的,尤其是写UI的时候,当我们点击一个按钮后V
- 我们知道HashMap集合是允许存放null值的hashMap是根据key的hashCode来寻找存放位置的,那当key为null时, 怎么
- 在前面的一篇文章中,简单的介绍了一下如何实现软键盘不自动弹出,使用的方法是设置android:wind
- 前言在这篇文章里,最后总结处,我说了会讲讲循环依赖中,其中一个类添加@Async有可能会导致注入失败而抛异常的情况,今天就分析一下。一、异常
- 本文主要和大家分享介绍了关于Java JDK * 使用的相关内容,分享出来供大家参考学习,下面来一起看看详细的介绍:前言代理是一种常用的
- 本文实例讲述了Android编程使用AlarmManager设置闹钟的方法。分享给大家供大家参考,具体如下:package com.Aina
- 本文实例讲述了C#使用文件流读取文件的方法。分享给大家供大家参考。具体如下:using System;using System.IO;nam
- 由其他进制转换为十进制比较简单,下面着重谈一谈十进制如何化为其他进制。1.使用Java带有的方法Integer,最简单粗暴了,代码如下//使
- C#Process OutputDataReceived事件不触发问题描述项目需要用cmd调用其它软件,实时获取软件处理结果,并根据获取到的
- 针对字符串是数字和字母结合而进行的,如"a20"和"a9";比较而得出结果是"a20&qu
- 场景网站智能问答场景,需要对多个分类查询,结果聚合展示由于每种分类都有自己的业务逻辑,有的需要查询数据库中间库,有的需要查询elastics
- 本文实例分析了C#中使用资源的方法。分享给大家供大家参考。具体如下:这里总结一个在C#中如何使用资源的方法如下:方法一、使用本地文件1、将本
- Spring Cloud 本地属性覆盖注:使用版本版本 spring cloud F SR2当前在项目中使用了Spring cloud 配置
- Java音频播放,因为必须依赖到本地环境,所以JAVA在音频处理方面优势不大,或者说打从Java体系开发时就没太多的考虑音频播放
- 本文实例为大家分享了Flutter自定义圆盘取色器的具体代码,供大家参考,具体内容如下下面展示一些 内联代码片。圆盘取色器效果图完整代码im