前面我们已经分析过Channel 的组成,其中我们了解到,最开始的时候ChannelPipeline 中含有两个ChannelHandlerContext(同时也是ChannelHandler),但是这个Pipeline 并不能实现什么特殊的功能,因为我们还没有给它添加自定义的ChannelHandler。通常来说,我们在初始化Bootstrap,会添加我们自定义的ChannelHandler,就以我们具体的客户端启动代码片段来举例:
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ChatClientHandler(nickName));
}
});
上面代码的初始化过程,相信大家都不陌生。在调用handler 时,传入了ChannelInitializer 对象,它提供了一个initChannel()方法给我我们初始化ChannelHandler。那么这个初始化过程是怎样的呢?下面我们来揭开它的神秘面纱。ChannelInitializer 实现了ChannelHandler,那么它是在什么时候添加到ChannelPipeline 中的呢?通过代码跟踪,我们发现它是在Bootstrap 的init()方法中添加到ChannelPipeline 中的,其代码如下:
void init(Channel channel) throws Exception {
ChannelPipeline p = channel.pipeline();
p.addLast(config.handler());
//略去N 句代码
}
从上面的代码可见,将handler()返回的ChannelHandler 添加到Pipeline 中,而handler()返回的其实就是我们在初始化Bootstrap 时通过handler()方法设置的ChannelInitializer 实例,因此这里就是将ChannelInitializer 插入到了Pipeline的末端。此时Pipeline 的结构如下图所示:
这时候,有小伙伴可能就有疑惑了,我明明插入的是一个ChannelInitializer 实例,为什么在ChannelPipeline 中的双向链表中的元素却是一个ChannelHandlerContext 呢?我们继续去源码中寻找答案。
刚才,我们提到,在Bootstrap 的init()方法中会调用p.addLast()方法,将ChannelInitializer 插入到链表的末端:
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler);
newCtx = newContext(group, filterName(name, handler), handler);
addLast0(newCtx);
// 略去N 句代码
return this;
}
private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}
addLast()有很多重载的方法,我们只需关注这个比较重要的方法就行。上面的addLast()方法中,首先检查ChannelHandler 的名字是否是重复,如果不重复,则调用newContex()方法为这个Handler 创建一个对应的DefaultChannelHandlerContext 实例,并与之关联起来(Context 中有一个handler 属性保存着对应的Handler 实例)。为了添加一个handler 到pipeline 中,必须把此handler 包装成ChannelHandlerContext。因此在上面的代码中我们可以看到新实例化了一个newCtx 对象,并将handler 作为参数传递到构造方法中。那么我们来看一下实例化的
DefaultChannelHandlerContext 到底有什么玄机吧。首先看它的构造器:
DefaultChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
if (handler == null) {
throw new NullPointerException("handler");
}
this.handler = handler;
}
在DefaultChannelHandlerContext 的构造器中,调用了两个很有意思的方法:isInbound()与isOutbound(),这两个方法是做什么的呢?
private static boolean isInbound(ChannelHandler handler) {
return handler instanceof ChannelInboundHandler;
}
private static boolean isOutbound(ChannelHandler handler) {
return handler instanceof ChannelOutboundHandler;
}
从源码中可以看到,当一个handler 实现了ChannelInboundHandler 接口,则isInbound 返回true;类似地,当一个handler 实现了ChannelOutboundHandler 接口,则isOutbound 就返回true。而这两个boolean 变量会传递到父类AbstractChannelHandlerContext 中,并初始化父类的两个字段:inbound 与outbound。
那么这里的ChannelInitializer 所对应的DefaultChannelHandlerContext 的inbound 与inbound 字段分别是什么呢? 那就看一下ChannelInitializer 到底实现了哪个接口不就行了?如下是ChannelInitializer 的类层次结构图:
从类图中可以清楚地看到,ChannelInitializer 仅仅实现了ChannelInboundHandler 接口,因此这里实例化的DefaultChannelHandlerContext 的inbound = true,outbound = false。
兜了一圈,不就是inbound 和outbound 两个字段嘛,为什么需要这么大费周折地分析一番?其实这两个字段关系到pipeline 的事件的流向与分类,因此是十分关键的,不过我在这里先卖个关子, 后面我们再来详细分析这两个字段所起的作用。至此, 我们暂时先记住一个结论:ChannelInitializer 所对应的DefaultChannelHandlerContext 的inbound = true,outbound = false。
当创建好Context 之后,就将这个Context 插入到Pipeline 的双向链表中,基础较差的可以将下面的逻辑用图画出来:
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}