前面我们已经分析了ChannelInitializer 是如何插入到Pipeline 中的,接下来就来探讨ChannelInitializer 在哪里被调用,ChannelInitializer 的作用以及我们自定义的ChannelHandler 是如何插入到Pipeline 中的。

先简单复习一下Channel 的注册过程:

1、首先在AbstractBootstrap 的initAndRegister()中,通过group().register(channel),调用MultithreadEventLoopGroup 的register()方法。

2、在MultithreadEventLoopGroup 的register()中调用next()获取一个可用的SingleThreadEventLoop,然后调用它的register()方法。

3、在SingleThreadEventLoop 的register()方法中,通过channel.unsafe().register(this, promise)方法获取channel的unsafe()底层IO 操作对象,然后调用它的register()。

4、在AbstractUnsafe 的register()方法中,调用register0()方法注册Channel 对象。

5、在AbstractUnsafe 的register0()方法中,调用AbstractNioChannel 的doRegister()方法。

6、AbstractNioChannel 的doRegister()方法调用javaChannel().register(eventLoop().selector, 0, this)将Channel对应的Java NIO 的SockerChannel 对象注册到一个eventLoop 的Selector 中,并且将当前Channel 作为attachment。

而我们自定义ChannelHandler 的添加过程,发生在AbstractUnsafe 的register0()方法中,在这个方法中调用了pipeline.fireChannelRegistered()方法,其代码实现如下:

public final ChannelPipeline fireChannelRegistered() {AbstractChannelHandlerContext.invokeChannelRegistered(head);return this;
}

再看AbstractChannelHandlerContext 的invokeChannelRegistered()方法:

static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {EventExecutor executor = next.executor();if (executor.inEventLoop()) {next.invokeChannelRegistered();} else {executor.execute(new Runnable() {@Overridepublic void run() {next.invokeChannelRegistered();}});}
}

很显然,这个代码会从head 开始遍历Pipeline 的双向链表,然后找到第一个属性inbound 为true 的ChannelHandlerContext 实例。想起来了没?我们在前面分析ChannelInitializer 时,花了大量的篇幅来分析了inbound和outbound 属性,现在这里就用上了。回想一下,ChannelInitializer 实现了ChannelInboudHandler,因此它所对应的ChannelHandlerContext 的inbound 属性就是true,因此这里返回就是ChannelInitializer 实例所对应的ChannelHandlerContext 对象,如下图所示:

当获取到inbound 的Context 后,就调用它的invokeChannelRegistered()方法:

private void invokeChannelRegistered() {if (invokeHandler()) {try {((ChannelInboundHandler) handler()).channelRegistered(this);} catch (Throwable t) {notifyHandlerException(t);}} else {fireChannelRegistered();}
}

我们已经知道,每个ChannelHandler 都和一个ChannelHandlerContext 关联,我们可以通过ChannelHandlerContext获取到对应的ChannelHandler。因此很明显,这里handler()返回的对象其实就是一开始我们实例化的ChannelInitializer 对象,并接着调用了ChannelInitializer 的channelRegistered()方法。看到这里, 应该会觉得有点眼熟了。ChannelInitializer 的channelRegistered()这个方法我们在一开始的时候已经接触到了,但是我们并没有深入地分析这个方法的调用过程。下面我们来看这个方法中到底有什么玄机,继续看代码:

public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {if (initChannel(ctx)) {ctx.pipeline().fireChannelRegistered();} else {ctx.fireChannelRegistered();}
}
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard againsttry {initChannel((C) ctx.channel());} catch (Throwable cause) {exceptionCaught(ctx, cause);} finally {remove(ctx);}return true;}return false;
}

initChannel((C) ctx.channel())这个方法我们也很熟悉,它就是我们在初始化Bootstrap 时,调用handler 方法传入的匿名内部类所实现的方法:

.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new ChatClientHandler(nickName));}
});

因此,当调用这个方法之后, 我们自定义的ChannelHandler 就插入到了Pipeline,此时Pipeline 的状态如下图所示:

当添加完成自定义的ChannelHandler 后,在finally 代码块会删除自定义的ChannelInitializer,也就是remove(ctx)最终调用ctx.pipeline().remove(this),因此最后的Pipeline 的状态如下:

至此,自定义ChannelHandler 的添加过程也分析得差不多了。

自定义ChannelHandler 的添加过程相关推荐

  1. Spring源码解析:自定义标签的解析过程

    2019独角兽企业重金招聘Python工程师标准>>> spring version : 4.3.x Spring 中的标签分为默认标签和自定义标签两类,上一篇我们探究了默认标签的解 ...

  2. joomla添加html,如何将自定义html代码添加到Joomla 2.5菜单项?

    这里是什么的Joomla提供了这样的情况: 在菜单管理 - >菜单项编辑 编辑每个菜单项,您将看到"链接类型选项"在右栏的部分. 见截图: 正如你在截图中看到,这组设置包含如 ...

  3. html表单输入框添加验证码,织梦Dedecms为自定义表单添加验证码功能

    使用织梦Dedecms自定义表单的时候,即使你做了字段的验证,也很有可能被人刷很多垃圾的内容,更加安全的一个方法是为自定义表单添加上验证码功能.今天我就来为大家分享一下怎样给自定义表单添加验证码! 一 ...

  4. WMS(一):Window的添加过程

    作者:刘望舒 链接:https://www.jianshu.com/p/aadfb70f25e2 前言 在此前的系列文章中我们学习了WindowManager体系和Window的属性,这一篇我们接着来 ...

  5. Android解析WindowManagerService(二)WMS的重要成员和Window的添加过程

    前言 在本系列的上一篇文章中,我们学习了WMS的诞生,WMS被创建后,它的重要的成员有哪些?Window添加过程的WMS部分做了什么呢?这篇文章会给你解答. 1.WMS的重要成员 所谓WMS的重要成员 ...

  6. amazon alexa_亚马逊使向自定义Alexa Skills添加声音变得更加容易

    amazon alexa by Terren Peterson 由Terren Peterson 亚马逊使向自定义Alexa Skills添加声音变得更加容易 (Amazon has made it ...

  7. Zabbix-server安装后的故障处理及Zabbix-agent的配置添加过程

    Zabbix-server安装后的故障处理及Zabbix-agent的配置添加过程 故障处理 第一种情况:在其他参数(Iptables.SeLinux等)配置正确的情况下,如果Web界面出现提示信息, ...

  8. vue-aMap高德地图的应用(添加覆盖物点坐标、自定义图标、添加信息窗体信息等)

    vue-aMap高德地图的应用(添加覆盖物点坐标.自定义图标.添加信息窗体信息等) 最近在项目开发中用到了aMap高德地图,简单记录一下,话不多说,直接上代码. 官方文档参考:高德地图aMap官方文档 ...

  9. Android R(11)为自定义HIDL接口添加DMFCM(六)

    为自定义HIDL接口添加DM&&FCM(六) 1.概览   引入HIDL的一个重要原因是Android团队想要将Android Framework和Android vendor之间的代 ...

最新文章

  1. java的不足_Java不足之我见
  2. 接口是什么意思_程序员天天用却不懂得冷知识,这两句口诀,让你理解RESTful接口...
  3. 总结之:CentOS6.5 DNS服务BIND配置、正反向解析、主从及压力测试(3)
  4. Effective C++ --5 实现
  5. 百度地图 key_Android百度地图导航的接入(包含驾车、公交、步行)
  6. Flutter Raw Image Provider
  7. 放大缩小保证div对齐_GraphPad Prism 绘图教程 | 如何在图表中对齐对象
  8. ewebeditor遍历路径漏洞
  9. win10开机密码忘记了的解锁教程
  10. Java 8 Iterable.forEach()与foreach循环
  11. SpringBoot +spring security 与CSRF有关的几个 问题
  12. Apache 模块 mod_cache应用
  13. (6)Redis的高可用方案
  14. 汇编程序求助,window.inc报错
  15. maven打包的时候同时打源码包,并同时将源码包上传私服
  16. 盘点JavaScript设计模式(常用十五大设计模式)
  17. 按键精灵文字识别插件_按键精灵课程学习目录
  18. 2020COSMO时尚盛典即将闪耀启幕
  19. android统计app使用时间段,GitHub - yaozs/UseTimeStatistic: Android 系统中统计各个app的使用时长以及使用次数...
  20. 并查集+思维——Destroying Array

热门文章

  1. 基于OpenGL的三种直线生成算法
  2. Git 提交规范-Java程序员收藏必备
  3. P2502 [HAOI2006]旅行 最小生成树
  4. Cisco packet tracer6.0下的网络工程实训
  5. .net Core 相关问题
  6. 【Java Web开发学习】Spring4条件化的bean
  7. 2014-2015-1学期使用的教材
  8. xenserver PXE安装系统错误的解决
  9. 【转自lzplzp】pair project总结
  10. 获取指定某一天的00:00—23:59