当一个请求进来的时候,ChannelPipeline 是如何调用内部的这些 handler 的呢?
当一个请求进来的时候,会第一个调用 pipeline 的 相关方法,如果是入站事件,这些方法由 fire 开头,表示开始管道的流动。从head节点开始,每个handler处理完成后,接着让后面的 handler 继续处理。
一、ChannelPipeline调度Handler的过程
在DefaultChannelPipeline中有个fireChannelActive()方法,是请求入站的开始,源码如下:
@Override
public final ChannelPipeline fireChannelActive() {
AbstractChannelHandlerContext.invokeChannelActive(head);
return this;
}
在有请求入站时,会先调用DefaultChannelPipeline的fireChannelActive()方法,从head节点开始往后执行,fireChannelActive()调用了AbstractChannelHandlerContext.invokeChannelActive(head):
//是否开启新线程执行invokeChannelActive()
static void invokeChannelActive(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelActive();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelActive();
}
});
}
}
接着调用了AbstractChannelHandlerContext的invokeChannelActive(),该方法会调用具体的Handler的channelActive()方法:
private void invokeChannelActive() {
if (invokeHandler()) {
try {
//调用具体Handler的channelActive()方法
((ChannelInboundHandler) handler()).channelActive(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
// 接着下一个handler继续执行
fireChannelActive();
}
}
需要注意的是,我们自己在实现ChannelInboundHandler的channelActive()方法时,如果我们想继续执行后面的handler,一定要在方法中调用ctx.fireChannelActive()才可以,否则后面的handler无法得到执行
:
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Thread.sleep(1000);
System.out.println(">>>ABossGroupHandler channelActive..." + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// 继续执行下一个handler,若不调用该方法,后面的handler不会执行
ctx.fireChannelActive();
}
调用ctx.fireChannelActive(),后又会调用invokeChannelActive(findContextInbound()):
@Override
public ChannelHandlerContext fireChannelActive() {
// 又回到最上面的那个方法上去了
invokeChannelActive(findContextInbound());
return this;
}
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
// 获取到下一个Context,继续执行
ctx = ctx.next;
} while (!ctx.inbound);
return ctx;
}
上面的方法调用中会会获取下一个Context,接着调用最上面的那个invokeChannelActive()方法,如此依次调用下来,直到调用到tailContext结束。过程如下图所示:
二、DefaultChannelPipeline的实现
2.1 DefaultChannelPipeline对ChannelInboundInvoker的实现
这里只罗列了fire开头的方法,这些方法都是具体事件发生时会触发的方法,这些事件发生后会通过DefaultChannelPipeline的fireXxx方法去调用具体Handler的对应方法,实现回调和用户业务逻辑的织入:
public class DefaultChannelPipeline implements ChannelPipeline{
@Override
public final ChannelPipeline fireChannelActive() {
AbstractChannelHandlerContext.invokeChannelActive(head);
return this;
}
@Override
public final ChannelPipeline fireChannelInactive() {
AbstractChannelHandlerContext.invokeChannelInactive(head);
return this;
}
@Override
public final ChannelPipeline fireExceptionCaught(Throwable cause) {
AbstractChannelHandlerContext.invokeExceptionCaught(head, cause);
return this;
}
@Override
public final ChannelPipeline fireUserEventTriggered(Object event) {
AbstractChannelHandlerContext.invokeUserEventTriggered(head, event);
return this;
}
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
@Override
public final ChannelPipeline fireChannelReadComplete() {
AbstractChannelHandlerContext.invokeChannelReadComplete(head);
return this;
}
@Override
public final ChannelPipeline fireChannelWritabilityChanged() {
AbstractChannelHandlerContext.invokeChannelWritabilityChanged(head);
return this;
}
}
这些fireXxx方法都是inbound的方法,也就是说都是入站事件发生时调用的方法,这里的head节点对象都体现为ChannelInboundInvoker类型的形态(从多态的角度来说,虽然head节点对象也实现了ChannelOutboundInvoker接口,但在此处它不体现ChannelOutboundInvoker的形态),然后通过AbstractChannelHandlerContext的相应静态方法调用ChannelInboundInvoker的具体实现的方法,最终调用到Handler的对应方法。
2.2 DefaultChannelPipeline对ChannelOutboundInvoker的实现
public class DefaultChannelPipeline implements ChannelPipeline{
@Override
public final ChannelFuture bind(SocketAddress localAddress) {
return tail.bind(localAddress);
}
@Override
public final ChannelFuture connect(SocketAddress remoteAddress) {
return tail.connect(remoteAddress);
}
@Override
public final ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
return tail.connect(remoteAddress, localAddress);
}
@Override
public final ChannelFuture disconnect() {
return tail.disconnect();
}
@Override
public final ChannelFuture close() {
return tail.close();
}
@Override
public final ChannelFuture deregister() {
return tail.deregister();
}
@Override
public final ChannelPipeline flush() {
tail.flush();
return this;
}
@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return tail.bind(localAddress, promise);
}
@Override
public final ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
return tail.connect(remoteAddress, promise);
}
@Override
public final ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
return tail.connect(remoteAddress, localAddress, promise);
}
@Override
public final ChannelFuture disconnect(ChannelPromise promise) {
return tail.disconnect(promise);
}
}
这些方法都是出站事件的实现,调用的都是outbound类型的tail节点的相应方法来处理的,都是outbound事件。
出站由tail开始,入站从head开始。因为出站是从内部向外写,从tail开始能够让前面的handler对后面的handler产生的信息进行处理,防止handler被遗漏。反之,入站是从head开始向内部输入,让后面的handler能够处理前面输入的数据。虽然head也实现了outbound接口,但不是从head开始执行出站任务。
三、ChannelPipeline 调度 handler 的总结
①Context 包装 handler,多个 Context 在 pipeline 中形成了双向链表,入站方向叫 inbound,由 head 节点开始,出站方向叫 outbound ,由 tail 节点开始。
②而节点间的传递通过 AbstractChannelHandlerContext 类内部的 fire 系列方法,找到当前节点的下一个节点不断的向后传播。以一个过滤器的模式完成对所有handler 的调度。