Netty源码分析篇:Netty Pipeline 初始化流程

Netty Pipeline 初始化流程详解

前言

在使用 Netty 开发网络应用时,Pipeline(管道)是一个非常核心的概念。它就像一条工厂流水线,负责处理进出的数据。

想象一下:你在工厂里组装手机,原材料从流水线一端进入,经过多个工位(检测、组装、测试、包装),最后从另一端输出成品。Netty 的 Pipeline 就是这样一条流水线,数据就是原材料,Handler 就是各个工位。

但你有没有想过这些问题:

  • 当我们调用 b.handler(new MyChannelInitializer()) 后,这个 Handler 是如何被添加到 Pipeline 中的?
  • 为什么我们重写的 initChannel() 方法会被自动调用?
  • 这个初始化过程到底在什么时候发生?

本文将带你深入 Netty 源码,一步步揭开 Pipeline 初始化的神秘面纱。即使你是 Netty 初学者,没有源码阅读经验,也能轻松理解整个流程。

什么是 Pipeline?

在深入源码之前,我们先用最简单的方式理解 Pipeline:

生活化类比

Pipeline 就像一条地铁线路

  • 地铁站 = Handler(处理站点)
  • 地铁列车 = 数据
  • 铁轨 = Pipeline(连接所有站点的轨道)

数据(列车)沿着 Pipeline(铁轨)依次经过每个 Handler(站点),每个站点都可以对数据进行处理。

技术定义

Pipeline 是 Netty 中责任链模式的实现,它维护了一个由多个 ChannelHandler 组成的双向链表。每个 Channel 都有自己的 Pipeline,用于处理入站(Inbound)和出站(Outbound)事件。

Pipeline 的基本结构如下:

head <-> handler1 <-> handler2 <-> ... <-> tail
  ↑                                        ↑
  |                                        |
Netty自动创建                          Netty自动创建

关键点

  • headtail 是 Netty 自动创建的两个特殊节点(哨兵节点)
  • 我们添加的 Handler 都会插入到它们之间
  • 数据可以从 head 流向 tail(入站),也可以从 tail 流向 head(出站)

整体流程概览

在深入细节之前,我们先建立一个全局视角,了解 Pipeline 初始化的整体流程。

两个阶段

Pipeline 的初始化主要分为两个阶段:

1. 准备阶段(Init)- “记账”

时机:在 Channel 创建时
动作:将 ChannelInitializer 添加到 Pipeline,但不执行初始化逻辑
类比:就像你在餐厅点菜,服务员把你的订单记录下来,但厨师还没开始做菜

2. 执行阶段(Register)- “做菜”

时机:在 Channel 注册到 EventLoop 后
动作:触发所有 Handler 的 handlerAdded() 方法,真正执行初始化
类比:厨师开始根据订单做菜,菜做好后端上桌

为什么要分两个阶段?

这是一个很重要的问题!让我用一个生活化的例子来解释:

场景:你要装修房子

  • 准备阶段:你和装修公司签合同,确定装修方案,但工人还没进场
  • 执行阶段:工人进场,水电工、木工、油漆工依次施工

为什么不能一步到位?

  • 工人还没到位(EventLoop 还没准备好)
  • 材料还没到齐(Channel 还没注册)
  • 施工条件不具备(线程环境不安全)

Netty 的设计思想
在 Channel 还没有注册到 EventLoop 之前,很多操作是不安全的(比如不知道在哪个线程执行)。所以 Netty 采用了一种**“延迟初始化”**的策略:

  1. 先把需要执行的任务记录下来(创建待处理任务)
  2. 等到合适的时机(Channel 注册完成)再统一执行

这种设计保证了线程安全执行顺序的正确性。

流程图预览

客户端启动
    ↓
b.connect() 调用
    ↓
创建 Channel
    ↓
【准备阶段】init(channel)
    ├─ 创建 Initializer-A(匿名 ChannelInitializer)
    ├─ 将 Initializer-A 添加到 Pipeline
    └─ 创建 PendingHandlerAddedTask(待处理任务)
    ↓
【执行阶段】register(channel)
    ├─ Channel 注册到 EventLoop
    ├─ 执行 PendingHandlerAddedTask
    ├─ 调用 Initializer-A.initChannel()
    │   └─ 将 MyChannelInitializer 添加到 Pipeline
    ├─ 调用 MyChannelInitializer.initChannel()
    │   └─ 执行用户的初始化逻辑
    └─ 初始化完成

接下来,我们从客户端启动代码开始,一步步追踪源码,看看这两个阶段是如何实现的。

第一阶段:准备阶段(Init)

1. 客户端启动入口

让我们从一个典型的 Netty 客户端启动代码开始:

private void connect(String inetHost, int inetPort) {
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    try {
        Bootstrap b = new Bootstrap();
        b.group(workerGroup);                          // 设置线程组
        b.channel(NioSocketChannel.class);             // 设置 Channel 类型
        b.option(ChannelOption.AUTO_READ, true);       // 设置选项
        b.handler(new MyChannelInitializer());         // ← 关键:设置 Handler
        
        // ← 关键:调用 connect 方法,触发初始化流程
        ChannelFuture f = b.connect(inetHost, inetPort).sync();
        
        f.channel().closeFuture().sync();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        workerGroup.shutdownGracefully();
    }
}

关键点理解

  1. b.handler(new MyChannelInitializer())

    • 这行代码只是把 MyChannelInitializer 保存到 Bootstrap 的配置中
    • 此时还没有添加到 Pipeline
    • 就像你把装修方案交给装修公司,但还没开始施工
  2. b.connect(inetHost, inetPort)

    • 这行代码会触发整个 Pipeline 的初始化流程
    • 就像你说"开工",装修公司开始安排工人进场

新手常见疑问

Q: 为什么 b.handler() 不直接添加到 Pipeline?
A: 因为此时 Channel 还没创建!Pipeline 是属于 Channel 的,没有 Channel 就没有 Pipeline。

2. connect() 方法调用链

当我们调用 b.connect() 时,会经过以下调用链:

// 第1步:Bootstrap.connect(String, int)
public ChannelFuture connect(String inetHost, int inetPort) {
    // 将主机名和端口转换为 SocketAddress
    return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
}

// 第2步:Bootstrap.connect(SocketAddress)
public ChannelFuture connect(SocketAddress remoteAddress) {
    if (remoteAddress == null) {
        throw new NullPointerException("remoteAddress");
    }
    validate();  // 验证配置是否完整
    // 继续调用
    return doResolveAndConnect(remoteAddress, config.localAddress());
}

// 第3步:Bootstrap.doResolveAndConnect()
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, 
                                          final SocketAddress localAddress) {
    // ← 关键方法:初始化并注册 Channel
    final ChannelFuture regFuture = initAndRegister();
    
    // ... 省略连接逻辑
    return regFuture;
}

调用链总结

connect(String, int) 
  → connect(SocketAddress) 
    → doResolveAndConnect() 
      → initAndRegister()  ← 核心入口

关键理解

  • 这个调用链最终会走到 initAndRegister() 方法
  • initAndRegister() 是 Pipeline 初始化的核心入口
  • 从方法名就能看出:init(初始化)+ Register(注册)= 两个阶段

3. initAndRegister() - 初始化和注册的入口

final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        // 步骤1:创建 Channel 实例(NioSocketChannel)
        // 就像建房子,先打地基
        channel = channelFactory.newChannel();
        
        // 步骤2:初始化 Channel(← 准备阶段的核心)
        // 就像装修房子,确定装修方案
        init(channel);
        
    } catch (Throwable t) {
        // ... 异常处理
    }
    
    // 步骤3:将 Channel 注册到 EventLoop(← 执行阶段的入口)
    // 就像房子装修完,开始入住
    ChannelFuture regFuture = config().group().register(channel);
    return regFuture;
}

这个方法做了三件事

步骤方法调用作用类比
1channelFactory.newChannel()创建 Channel 实例建房子打地基
2init(channel)初始化 Channel(准备阶段)确定装修方案
3register(channel)注册到 EventLoop(执行阶段)工人进场施工

重点关注:我们重点关注第 2 步的 init() 方法,这是准备阶段的核心。

4. init() - 将 ChannelInitializer 添加到 Pipeline

@Override
void init(Channel channel) throws Exception {
    // 获取 Channel 的 Pipeline
    ChannelPipeline p = channel.pipeline();
    
    // ← 关键:创建一个匿名 ChannelInitializer(我们称它为 Initializer-A)
    // 并将它添加到 Pipeline
    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(final Channel ch) throws Exception {
            final ChannelPipeline pipeline = ch.pipeline();
            
            // 获取用户设置的 Handler(就是我们的 MyChannelInitializer)
            ChannelHandler handler = config.handler();
            if (handler != null) {
                // 将用户的 Handler 添加到 Pipeline
                pipeline.addLast(handler);
            }
            
            // ... 其他初始化逻辑
        }
    });
}

关键点理解

  1. 创建了一个匿名 ChannelInitializer

    • 我们称它为 Initializer-A(框架层的初始化器)
    • 它的作用是:将用户的 Handler(MyChannelInitializer)添加到 Pipeline
    • 这是 Netty 框架自己创建的,不是我们写的
  2. 调用了 p.addLast()

    • 将 Initializer-A 添加到 Pipeline 中
    • 但是!Initializer-A 的 initChannel() 方法还没有被调用
    • 这就是"延迟初始化"的体现
  3. 此时 Pipeline 的状态

    head <-> Initializer-A(待执行) <-> tail
    

新手常见疑问

Q: 为什么要创建 Initializer-A,不直接添加 MyChannelInitializer?
A: 因为 Netty 需要在添加用户 Handler 前后做一些框架层的处理(比如添加 ServerBootstrapAcceptor)。Initializer-A 就是这个"中间层",负责协调框架逻辑和用户逻辑。

Q: 为什么 initChannel() 方法还没被调用?
A: 因为此时 Channel 还没注册到 EventLoop,执行环境还不安全。Netty 会等到注册完成后再调用。

5. addLast() - 添加 Handler 的核心逻辑

让我们深入 addLast() 方法,看看 Netty 是如何处理 Handler 添加的。这是整个准备阶段最核心的方法!

@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    
    synchronized (this) {
        // 前置检查:Handler 是否可以被多次添加
        checkMultiplicity(handler);
        
        // ========== 步骤1:包装 Handler ==========
        // 将 Handler 包装成 Context
        newCtx = newContext(group, filterName(name, handler), handler);
        
        // ========== 步骤2:添加到链表 ==========
        // 将 Context 添加到 Pipeline 的双向链表中
        addLast0(newCtx);
        
        // ========== 步骤3:延迟回调(关键!)==========
        // 判断 Channel 是否已经注册
        if (!registered) {
            // 如果还没注册,标记为"待添加"状态
            newCtx.setAddPending();
            
            // ← 核心:创建待处理任务,延迟到注册后执行
            callHandlerCallbackLater(newCtx, true);
            return this;
        }
        
        // 如果已经注册,直接调用 handlerAdded
        EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
            // 不在 EventLoop 线程中,提交任务
            callHandlerAddedInEventLoop(newCtx, executor);
            return this;
        }
    }
    
    // 在 EventLoop 线程中,直接调用
    callHandlerAdded0(newCtx);
    return this;
}

这个方法是 Pipeline 初始化的核心中的核心,让我们逐步分析每个步骤。

步骤1:newContext() - 包装 Handler
newCtx = newContext(group, filterName(name, handler), handler);

作用:将 ChannelHandler 包装成 AbstractChannelHandlerContext。

为什么要包装?

这是一个非常重要的设计!让我用一个生活化的例子来解释:

类比:Handler 就像一个工人,Context 就像工人的工位

  • Handler(工人):只负责干活(处理数据)
  • Context(工位)
    • 知道自己的位置(前一个工位、后一个工位)
    • 知道自己的工具(执行器 Executor)
    • 持有工人的引用(Handler 引用)

技术理解

  • Pipeline 实际上是一个 Context 的双向链表,而不是 Handler 的链表
  • 每个 Context 都持有一个 Handler 的引用
  • Context 还包含了前后节点的引用、执行器等信息
Handler(业务逻辑)
    ↓ 被包装
Context(容器 + 位置信息 + Handler引用)
    ↓ 组成链表
Pipeline(Context 双向链表)
步骤2:addLast0() - 添加到链表
addLast0(newCtx);

作用:将新创建的 Context 添加到 Pipeline 的双向链表末尾(tail 之前)。

链表操作示意

添加前:head <-> tail
添加后:head <-> newCtx <-> tail

这个方法会修改链表的指针,将新节点插入到 tail 之前。

步骤3:callHandlerCallbackLater() - 延迟回调(最关键!)
if (!registered) {
    newCtx.setAddPending();
    callHandlerCallbackLater(newCtx, true);
    return this;
}

关键判断if (!registered)

这是整个准备阶段最关键的判断!

判断逻辑

  • registered = false:Channel 还没注册到 EventLoop

    • 动作:创建待处理任务,延迟执行
    • 场景:Initializer-A 就是这种情况
  • registered = true:Channel 已经注册到 EventLoop

    • 动作:直接调用 callHandlerAdded0(),立即执行
    • 场景:MyChannelInitializer 就是这种情况(稍后会讲到)

为什么要延迟?

让我用一个生活化的例子来解释:

场景:你要开车去旅行

  • 情况1:车还没发动(Channel 还没注册)

    • 你不能立即开车,只能先记下"要去哪里"(创建待处理任务)
    • 等车发动后再出发(注册完成后执行任务)
  • 情况2:车已经发动(Channel 已经注册)

    • 你可以立即开车出发(直接执行初始化)

技术理解

  • 在 Channel 还没注册到 EventLoop 之前,不知道在哪个线程执行
  • 直接执行可能导致线程安全问题
  • 所以先"记账"(创建任务),等环境准备好再"结账"(执行任务)

6. callHandlerCallbackLater() - 创建待处理任务

让我们看看这个方法的实现:

private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
    assert !registered;  // 断言:确保还没注册
    
    // 根据 added 参数创建不同的任务
    // added = true:创建 PendingHandlerAddedTask(Handler 添加任务)
    // added = false:创建 PendingHandlerRemovedTask(Handler 移除任务)
    PendingHandlerCallback task = added ? 
        new PendingHandlerAddedTask(ctx) : 
        new PendingHandlerRemovedTask(ctx);
    
    // 将任务加入到待处理队列(单向链表)
    PendingHandlerCallback pending = pendingHandlerCallbackHead;
    if (pending == null) {
        // 队列为空,设置为头节点
        pendingHandlerCallbackHead = task;
    } else {
        // 队列不为空,添加到队列尾部
        while (pending.next != null) {
            pending = pending.next;
        }
        pending.next = task;
    }
}

核心思想

这个方法创建了一个 PendingHandlerAddedTask 任务,并将它加入到一个待处理任务队列中。

队列结构

pendingHandlerCallbackHead → Task1 → Task2 → Task3 → null
                              ↑
                              |
                    我们刚创建的 PendingHandlerAddedTask

为什么要用任务队列?

让我用一个生活化的例子来解释:

场景:餐厅点餐系统

  • 顾客点菜(添加 Handler):服务员把订单写在小票上
  • 订单队列(待处理任务队列):所有小票按顺序夹在夹子上
  • 厨师做菜(执行任务):厨师按顺序取小票,依次做菜

为什么不能立即做菜?

  • 厨师还没准备好(EventLoop 还没准备好)
  • 食材还没到齐(Channel 还没注册)
  • 厨房还没开火(线程环境不安全)

技术理解
在 Channel 还没有注册到 EventLoop 之前,很多操作是不安全的:

  • 无法确定在哪个线程执行
  • EventLoop 还没有准备好
  • 可能会导致线程安全问题

所以 Netty 采用了一种巧妙的设计:

  1. 先记账:把需要执行的操作记录下来(创建任务)
  2. 后结账:等到 Channel 注册完成后,再统一执行这些任务

准备阶段小结

到这里,准备阶段就完成了。此时的状态是:

  1. ✅ Initializer-A 已经被添加到 Pipeline 链表中
  2. ✅ 一个 PendingHandlerAddedTask 任务被创建并加入到待处理队列
  3. ❌ 但是 Initializer-A 的 initChannel() 方法还没有被调用

状态快照

Pipeline 链表:
    head <-> ctx-A(持有 Initializer-A) <-> tail

待处理任务队列:
    pendingHandlerCallbackHead → PendingHandlerAddedTask(ctx-A) → null

Initializer-A 状态:
    ✅ 已添加到 Pipeline
    ❌ initChannel() 未调用
    ⏳ 等待 Channel 注册完成

接下来,我们进入第二阶段:执行阶段。

第二阶段:执行阶段(Register)

执行阶段概览

准备阶段完成后,我们已经"记好账"了(创建了待处理任务)。现在进入执行阶段,开始"结账"(执行这些任务)。

执行阶段的核心流程

register(channel)
    ↓
register0()
    ↓
doRegister()  ← 将 Channel 注册到 Selector
    ↓
pipeline.invokeHandlerAddedIfNeeded()  ← 触发待处理任务
    ↓
callHandlerAddedForAllHandlers()  ← 执行所有待处理任务
    ↓
PendingHandlerAddedTask.execute()  ← 执行具体任务
    ↓
callHandlerAdded0(ctx-A)  ← 调用 Initializer-A 的 handlerAdded
    ↓
Initializer-A.initChannel()  ← 执行 Initializer-A 的初始化逻辑
    ↓
pipeline.addLast(MyChannelInitializer)  ← 添加用户的 Handler
    ↓
MyChannelInitializer.initChannel()  ← 执行用户的初始化逻辑
    ↓
初始化完成!

让我们逐步分析每个环节。

7. register() - 注册 Channel 到 EventLoop

回到 initAndRegister() 方法,在调用完 init() 后,会继续执行注册操作:

final ChannelFuture initAndRegister() {
  Channel channel = null;
  try {
      channel = channelFactory.newChannel();
      init(channel);  // 准备阶段完成
  } catch (Throwable t) {
      // ...
  }
  
  // 执行阶段开始:注册 Channel
  ChannelFuture regFuture = config().group().register(channel);
  return regFuture;
}

让我们追踪 register() 方法的调用链:

// MultithreadEventLoopGroup.register()
@Override
public ChannelFuture register(Channel channel) {
  return register(new DefaultChannelPromise(channel, this));
}

// SingleThreadEventLoop.register()
@Override
public ChannelFuture register(final ChannelPromise promise) {
  ObjectUtil.checkNotNull(promise, "promise");
  // 调用 Channel 的 Unsafe 进行注册
  promise.channel().unsafe().register(this, promise);
  return promise;
}

// AbstractChannel.AbstractUnsafe.register()
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
  // ... 省略一些检查和设置
  
  // 判断是否在 EventLoop 线程中
  if (eventLoop.inEventLoop()) {
      register0(promise);
  } else {
      // 如果不在,提交任务到 EventLoop
      eventLoop.execute(new Runnable() {
          @Override
          public void run() {
              register0(promise);
          }
      });
  }
}

最终会调用到 register0() 方法。

8. register0() - 真正的注册逻辑

private void register0(ChannelPromise promise) {
  try {
      // 确保 Channel 还是打开状态
      if (!promise.setUncancellable() || !ensureOpen(promise)) {
          return;
      }
      
      boolean firstRegistration = neverRegistered;
      
      // 执行底层的注册操作(将 Channel 注册到 Selector)
      doRegister();
      
      neverRegistered = false;
      registered = true;
      
      // 关键:调用 Pipeline 的 invokeHandlerAddedIfNeeded()
      pipeline.invokeHandlerAddedIfNeeded();
      
      // 触发 channelRegistered 事件
      safeSetSuccess(promise);
      pipeline.fireChannelRegistered();
      
      // ... 其他逻辑
  } catch (Throwable t) {
      // ... 异常处理
  }
}

关键代码pipeline.invokeHandlerAddedIfNeeded()

这行代码会触发所有待处理任务的执行,也就是我们在准备阶段创建的 PendingHandlerAddedTask

9. invokeHandlerAddedIfNeeded() - 触发待处理任务

final void invokeHandlerAddedIfNeeded() {
  assert channel.eventLoop().inEventLoop();
  
  if (firstRegistration) {
      firstRegistration = false;
      // 调用所有待处理的 handlerAdded 回调
      callHandlerAddedForAllHandlers();
  }
}

作用:检查是否是第一次注册,如果是,则调用 callHandlerAddedForAllHandlers() 执行所有待处理任务。

10. callHandlerAddedForAllHandlers() - 执行所有待处理任务

private void callHandlerAddedForAllHandlers() {
  final PendingHandlerCallback pendingHandlerCallbackHead;
  
  synchronized (this) {
      assert !registered;
      
      // 标记为已注册
      registered = true;
      
      // 获取待处理任务队列的头节点
      pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
      
      // 清空队列(帮助 GC)
      this.pendingHandlerCallbackHead = null;
  }
  
  // 遍历任务队列,执行每个任务
  PendingHandlerCallback task = pendingHandlerCallbackHead;
  while (task != null) {
      task.execute();  // 执行任务
      task = task.next;  // 移动到下一个任务
  }
}

核心逻辑

1. 将 `registered` 标记设置为 `true`,表示 Channel 已经注册
2. 获取待处理任务队列的头节点
3. 遍历队列,依次执行每个任务的 `execute()` 方法

这里的 task 就是我们在准备阶段创建的 PendingHandlerAddedTask

11. PendingHandlerAddedTask.execute() - 执行添加任务

让我们看看 PendingHandlerAddedTaskexecute() 方法:

@Override
void execute() {
  EventExecutor executor = ctx.executor();
  
  // 判断是否在 EventLoop 线程中
  if (executor.inEventLoop()) {
      // 在 EventLoop 线程中,直接调用
      callHandlerAdded0(ctx);
  } else {
      // 不在 EventLoop 线程中,提交任务
      try {
          executor.execute(this);
      } catch (RejectedExecutionException e) {
          if (logger.isWarnEnabled()) {
              logger.warn(
                  "Can't invoke handlerAdded() as the EventExecutor {} rejected it, removing handler {}.",
                  executor, ctx.name(), e);
          }
          remove0(ctx);
          ctx.setRemoved();
      }
  }
}

作用

这个方法确保 callHandlerAdded0() 在正确的线程(EventLoop 线程)中执行。如果当前已经在 EventLoop 线程中,就直接调用;否则,将任务提交到 EventLoop 中执行。

在我们的场景中,由于 register0() 已经在 EventLoop 线程中执行,所以会直接调用 callHandlerAdded0(ctx)

12. callHandlerAdded0() - 调用 handlerAdded 回调

private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
  try {
      // 调用 Context 的 callHandlerAdded 方法
      ctx.callHandlerAdded();
  } catch (Throwable t) {
      boolean removed = false;
      try {
          // 如果出现异常,移除这个 Handler
          remove0(ctx);
          ctx.callHandlerRemoved();
          removed = true;
      } catch (Throwable t2) {
          if (logger.isWarnEnabled()) {
              logger.warn("Failed to remove a handler: " + ctx.name(), t2);
          }
      }
      
      // 触发异常事件
      if (removed) {
          fireExceptionCaught(new ChannelPipelineException(
              ctx.handler().getClass().getName() +
              ".handlerAdded() has thrown an exception; removed.", t));
      } else {
          fireExceptionCaught(new ChannelPipelineException(
              ctx.handler().getClass().getName() +
              ".handlerAdded() has thrown an exception; also failed to remove.", t));
      }
  }
}

作用

调用 Context 的 callHandlerAdded() 方法,并处理可能出现的异常。如果 handlerAdded() 方法抛出异常,会自动将这个 Handler 从 Pipeline 中移除。

13. AbstractChannelHandlerContext.callHandlerAdded() - 最终调用

final void callHandlerAdded() throws Exception {
  // 设置状态为 ADD_COMPLETE
  // 这个方法会检查状态,确保 handlerAdded 只被调用一次
  if (setAddComplete()) {
      // 调用 Handler 的 handlerAdded 方法
      handler().handlerAdded(this);
  }
}

关键点handler().handlerAdded(this)

这行代码会调用当前 Context 持有的 Handler 的 handlerAdded() 方法。

重要说明:这里调用的是 Bootstrap.init() 方法中创建的匿名 ChannelInitializer(Initializer-A)的 handlerAdded() 方法,而不是我们自定义的 MyChannelInitializer。

为什么呢?因为:

1. 在准备阶段,`p.addLast(Initializer-A)` 创建了 ctx-A
2. ctx-A 持有的 handler 引用指向 Initializer-A
3. PendingHandlerAddedTask 持有的是 ctx-A 的引用
4. 所以这里的 `handler()` 返回的是 Initializer-A

Initializer-A 的 handlerAdded() 方法会:

1. 调用 Initializer-A 重写的 `initChannel()` 方法
2. 在 `initChannel()` 中,将我们的 MyChannelInitializer 添加到 Pipeline
3. MyChannelInitializer 被添加后,也会触发它的 `handlerAdded()` 方法
4. 最终调用到我们重写的 `initChannel()` 方法

接下来,我们详细分析这个调用流程。

深入理解:initChannel() 方法的调用机制

核心问题回顾

在第 13 步中,我们看到 handler().handlerAdded(this) 被调用。这里有一个非常重要的问题需要理解:

handler().handlerAdded(this) 被调用时,调用的是哪个 ChannelInitializer 实例的方法?

答案是:调用的是 Bootstrap.init() 方法中创建的匿名 ChannelInitializer(Initializer-A)实例的 handlerAdded() 方法

理解关键:Context 持有的是哪个 Handler?

回顾 Pipeline 初始化的准备阶段

Bootstrap.init() 方法中,Netty 创建了一个匿名 ChannelInitializer:

@Override
void init(Channel channel) throws Exception {
  ChannelPipeline p = channel.pipeline();
  
  // 创建匿名 ChannelInitializer(我们称它为 Initializer-A)
  p.addLast(new ChannelInitializer<Channel>() {
      @Override
      public void initChannel(final Channel ch) throws Exception {
          final ChannelPipeline pipeline = ch.pipeline();
          // 获取用户设置的 handler(MyChannelInitializer)
          ChannelHandler handler = config.handler();
          if (handler != null) {
              // 将 MyChannelInitializer 添加到 Pipeline
              pipeline.addLast(handler);
          }
      }
  });
}
关键点:addLast() 做了什么?

当调用 p.addLast(new ChannelInitializer<Channel>() {...}) 时:

1. 创建了一个新的 Context(我们称它为 ctx-A)
2. ctx-A 持有的 handler 引用指向这个匿名 ChannelInitializer(Initializer-A)
3. ctx-A 被添加到 Pipeline 链表中
4. 创建了一个 PendingHandlerAddedTask,持有 ctx-A 的引用

此时的状态:

Pipeline: head <-> ctx-A(持有 Initializer-A) <-> tail
待处理任务: PendingHandlerAddedTask(ctx-A)
执行阶段:调用 handlerAdded

当 Channel 注册完成后,会执行待处理任务:

// PendingHandlerAddedTask.execute()
void execute() {
  callHandlerAdded0(ctx);  // 这里的 ctx 就是 ctx-A
}

// callHandlerAdded0
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
  ctx.callHandlerAdded();  // 调用 ctx-A 的 callHandlerAdded
}

// AbstractChannelHandlerContext.callHandlerAdded
final void callHandlerAdded() throws Exception {
  if (setAddComplete()) {
      // 这里的 handler() 返回的是 ctx-A 持有的 handler
      // 也就是 Initializer-A(匿名 ChannelInitializer)
      handler().handlerAdded(this);
  }
}

关键理解

  • handler() 方法返回的是当前 Context 持有的 Handler 引用
  • ctx-A 持有的是 Initializer-A(匿名 ChannelInitializer)
  • 所以 handler().handlerAdded(this) 调用的是 Initializer-A 的 handlerAdded() 方法

Initializer-A 的完整调用流程

现在让我们完整地追踪 Initializer-A 的 handlerAdded() 方法是如何最终调用到我们自定义的 MyChannelInitializer 的 initChannel() 方法的。

步骤1:调用 Initializer-A 的 handlerAdded()
// ChannelInitializer.handlerAdded()
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
  // 检查 Channel 是否已经注册
  if (ctx.channel().isRegistered()) {
      // 调用 initChannel(ctx)
      if (initChannel(ctx)) {
          // 初始化成功后,移除自己
          removeState(ctx);
      }
  }
}

作用

这个方法是 ChannelInitializer 的核心方法,它会:

1. 检查 Channel 是否已经注册(此时已经注册)
2. 调用 `initChannel(ctx)` 方法
3. 初始化完成后,将自己从 Pipeline 中移除
步骤2:调用 initChannel(ChannelHandlerContext)
// ChannelInitializer.initChannel(ChannelHandlerContext)
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
  // 使用 Set 确保每个 Context 只初始化一次
  if (initMap.add(ctx)) {
      try {
          // 关键:调用抽象方法 initChannel(Channel)
          // 由于多态,这里会调用子类(Initializer-A)重写的版本
          initChannel((C) ctx.channel());
      } catch (Throwable cause) {
          // 如果初始化过程中出现异常,触发异常处理
          exceptionCaught(ctx, cause);
      } finally {
          // 无论成功还是失败,都要将 ChannelInitializer 从 Pipeline 中移除
          ChannelPipeline pipeline = ctx.pipeline();
          if (pipeline.context(this) != null) {
              pipeline.remove(this);
          }
      }
      return true;
  }
  return false;
}

作用

这个方法做了三件重要的事:

1. 使用 `initMap` 确保每个 Context 只初始化一次(防止重复初始化)
2. 调用抽象方法 `initChannel(Channel)`,由于 Java 的多态机制,会调用 Initializer-A 重写的版本
3. 在 finally 块中移除 ChannelInitializer,确保它只执行一次

为什么要移除?

ChannelInitializer 是一个"一次性"的 Handler,它的唯一作用就是在合适的时机初始化 Pipeline。一旦初始化完成,它就没有存在的必要了,所以要从 Pipeline 中移除。

步骤3:调用 Initializer-A 重写的 initChannel(Channel)
// 这是在 Bootstrap.init() 中定义的匿名内部类
new ChannelInitializer<Channel>() {
  @Override
  public void initChannel(final Channel ch) throws Exception {
      final ChannelPipeline pipeline = ch.pipeline();
      
      // 获取用户设置的 handler(MyChannelInitializer)
      ChannelHandler handler = config.handler();
      
      if (handler != null) {
          // 将 MyChannelInitializer 添加到 Pipeline
          pipeline.addLast(handler);
      }
      
      // 注意:这里可能还有其他逻辑,比如添加 ServerBootstrapAcceptor
      // 但对于客户端来说,主要就是添加用户的 handler
  }
}

作用

这是 Initializer-A 重写的 initChannel() 方法,它的作用是:

1. 获取用户通过 `b.handler()` 设置的 Handler(也就是 MyChannelInitializer)
2. 将 MyChannelInitializer 添加到 Pipeline 中

关键理解

  • config.handler() 返回的是我们在客户端启动代码中设置的 new MyChannelInitializer()
  • 这里调用 pipeline.addLast(handler) 会将 MyChannelInitializer 添加到 Pipeline

此时 Pipeline 的状态变化:

之前:head <-> Initializer-A <-> tail
之后:head <-> MyChannelInitializer <-> tail
(Initializer-A 在 finally 块中被移除)

MyChannelInitializer 的初始化流程

现在,MyChannelInitializer 已经被添加到 Pipeline 中了。接下来,它也会经历同样的初始化流程。

步骤4:addLast(MyChannelInitializer)

当调用 pipeline.addLast(handler) 时(这里的 handler 是 MyChannelInitializer):

@Override
public final ChannelPipeline addLast(ChannelHandler... handlers) {
  return addLast(null, handlers);
}

public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
  if (handlers == null) {
      throw new NullPointerException("handlers");
  }
  
  for (ChannelHandler h: handlers) {
      if (h == null) {
          break;
      }
      // 遍历每个 Handler,逐个添加
      addLast(executor, null, h);
  }
  
  return this;
}

最终会调用到:

@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
  final AbstractChannelHandlerContext newCtx;
  
  synchronized (this) {
      // 检查 Handler 是否可以被多次添加
      checkMultiplicity(handler);
      
      // 将 MyChannelInitializer 包装成 Context(我们称它为 ctx-B)
      newCtx = newContext(group, filterName(name, handler), handler);
      
      // 将 ctx-B 添加到 Pipeline 链表中
      addLast0(newCtx);
      
      // 关键判断:此时 Channel 已经注册(registered = true)
      if (!registered) {
          // 如果还没注册,创建待处理任务
          newCtx.setAddPending();
          callHandlerCallbackLater(newCtx, true);
          return this;
      }
      
      // 因为已经注册,所以直接调用 handlerAdded
      EventExecutor executor = newCtx.executor();
      if (!executor.inEventLoop()) {
          // 如果不在 EventLoop 线程中,提交任务
          callHandlerAddedInEventLoop(newCtx, executor);
          return this;
      }
  }
  
  // 在 EventLoop 线程中,直接调用
  callHandlerAdded0(newCtx);
  return this;
}

关键区别

与 Initializer-A 不同,此时 Channel 已经注册(registered = true),所以不会创建待处理任务,而是直接调用 callHandlerAdded0(newCtx)

步骤5:调用 callHandlerAdded0(ctx-B)
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
  try {
      // 调用 ctx-B 的 callHandlerAdded
      ctx.callHandlerAdded();
  } catch (Throwable t) {
      // 异常处理:如果出现异常,移除这个 Handler
      boolean removed = false;
      try {
          remove0(ctx);
          ctx.callHandlerRemoved();
          removed = true;
      } catch (Throwable t2) {
          if (logger.isWarnEnabled()) {
              logger.warn("Failed to remove a handler: " + ctx.name(), t2);
          }
      }
      
      // 触发异常事件
      if (removed) {
          fireExceptionCaught(new ChannelPipelineException(
              ctx.handler().getClass().getName() +
              ".handlerAdded() has thrown an exception; removed.", t));
      } else {
          fireExceptionCaught(new ChannelPipelineException(
              ctx.handler().getClass().getName() +
              ".handlerAdded() has thrown an exception; also failed to remove.", t));
      }
  }
}

作用

调用 ctx-B 的 callHandlerAdded() 方法,并处理可能出现的异常。

步骤6:调用 ctx-B.callHandlerAdded()
final void callHandlerAdded() throws Exception {
  // 设置状态为 ADD_COMPLETE,确保 handlerAdded 只被调用一次
  if (setAddComplete()) {
      // 这里的 handler() 返回的是 ctx-B 持有的 handler
      // 也就是 MyChannelInitializer
      handler().handlerAdded(this);
  }
}

关键理解

  • 这里的 handler() 返回的是 ctx-B 持有的 Handler
  • ctx-B 持有的是 MyChannelInitializer
  • 所以这里调用的是 MyChannelInitializer 的 handlerAdded() 方法
步骤7:调用 MyChannelInitializer 的 handlerAdded()
// ChannelInitializer.handlerAdded()
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
  if (ctx.channel().isRegistered()) {
      // 调用 initChannel(ctx)
      if (initChannel(ctx)) {
          removeState(ctx);
      }
  }
}

这个方法和 Initializer-A 的 handlerAdded() 是同一个方法(都是 ChannelInitializer 类的方法)。

步骤8:调用 initChannel(ChannelHandlerContext)
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
  if (initMap.add(ctx)) {
      try {
          // 调用抽象方法 initChannel(Channel)
          // 由于多态,这里会调用 MyChannelInitializer 重写的版本
          initChannel((C) ctx.channel());
      } catch (Throwable cause) {
          exceptionCaught(ctx, cause);
      } finally {
          // 将 MyChannelInitializer 从 Pipeline 中移除
          ChannelPipeline pipeline = ctx.pipeline();
          if (pipeline.context(this) != null) {
              pipeline.remove(this);
          }
      }
      return true;
  }
  return false;
}
步骤9:调用 MyChannelInitializer 重写的 initChannel(Channel)
// 这是我们自定义的 MyChannelInitializer
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
  @Override
  protected void initChannel(SocketChannel channel) throws Exception {
      // 这里是我们的业务逻辑
      System.out.println("链接报告开始");
      System.out.println("链接报告信息:本客户端链接到服务端。channelId:" + channel.id());
      System.out.println("链接报告完毕");
      
      // 我们可以在这里添加其他 Handler
      // channel.pipeline().addLast(new StringDecoder());
      // channel.pipeline().addLast(new StringEncoder());
      // channel.pipeline().addLast(new MyBusinessHandler());
  }
}

作用

这就是我们自定义的初始化逻辑!在这里,我们可以:

1. 打印日志
2. 添加编解码器
3. 添加业务 Handler
4. 进行其他初始化操作

执行完成后,MyChannelInitializer 也会在 finally 块中被移除。

完整流程图

让我们用一张图来总结整个调用流程:

时间线:T1 - 客户端启动
  |
  v
b.handler(new MyChannelInitializer())
  [MyChannelInitializer 实例被保存在 config 中]

-------------------------------------------------------------------

时间线:T2 - 调用 b.connect()
  |
  v
Bootstrap.init(channel)
  |
  v
创建 Initializer-A(匿名 ChannelInitializer)
  |
  v
p.addLast(Initializer-A)
  |
  v
创建 ctx-A,持有 Initializer-A 的引用
  |
  v
将 ctx-A 添加到 Pipeline
  |
  v
创建 PendingHandlerAddedTask(ctx-A)

Pipeline 状态:head <-> ctx-A(Initializer-A) <-> tail

-------------------------------------------------------------------

时间线:T3 - Channel 注册完成
  |
  v
执行 PendingHandlerAddedTask
  |
  v
callHandlerAdded0(ctx-A)
  |
  v
ctx-A.callHandlerAdded()
  |
  v
ctx-A.handler().handlerAdded(ctx-A)
  [这里的 handler() 返回 Initializer-A]
  |
  v
Initializer-A.handlerAdded(ctx-A)
  |
  v
Initializer-A.initChannel(ctx-A)
  |
  v
Initializer-A.initChannel(channel)  ← 调用匿名内部类重写的方法
  |
  v
获取 config.handler()  [返回 MyChannelInitializer]
  |
  v
pipeline.addLast(MyChannelInitializer)
  |
  v
Initializer-A 被移除

Pipeline 状态:head <-> ctx-B(MyChannelInitializer) <-> tail

-------------------------------------------------------------------

时间线:T4 - MyChannelInitializer 被添加
  |
  v
创建 ctx-B,持有 MyChannelInitializer 的引用
  |
  v
因为 registered = true,直接调用 callHandlerAdded0(ctx-B)
  |
  v
ctx-B.callHandlerAdded()
  |
  v
ctx-B.handler().handlerAdded(ctx-B)
  [这里的 handler() 返回 MyChannelInitializer]
  |
  v
MyChannelInitializer.handlerAdded(ctx-B)
  |
  v
MyChannelInitializer.initChannel(ctx-B)
  |
  v
MyChannelInitializer.initChannel(channel)  ← 调用我们重写的方法
  |
  v
执行我们的业务逻辑:
  - 打印日志
  - 添加其他 Handler
  |
  v
MyChannelInitializer 被移除

最终 Pipeline 状态:head <-> [我们添加的 Handler] <-> tail

核心要点总结

1. 两个不同的 ChannelInitializer 实例
  • Initializer-A:Bootstrap.init() 中创建的匿名内部类

    • 作用:将用户设置的 Handler(MyChannelInitializer)添加到 Pipeline
    • 生命周期:在 register 完成后被调用,执行完立即移除
  • MyChannelInitializer:用户自定义的 ChannelInitializer

    • 作用:执行用户的初始化逻辑,添加业务 Handler
    • 生命周期:被 Initializer-A 添加到 Pipeline 后立即被调用,执行完立即移除
2. 为什么 callHandlerAdded() 调用的是 Initializer-A?

因为:

1. 在准备阶段,`p.addLast(Initializer-A)` 创建了 ctx-A
2. ctx-A 持有的 handler 引用指向 Initializer-A
3. PendingHandlerAddedTask 持有的是 ctx-A 的引用
4. 执行阶段调用 `ctx-A.handler().handlerAdded()` 时,返回的是 Initializer-A

关键理解:Context 持有哪个 Handler 的引用,handler() 方法就返回哪个 Handler。

3. registered 标志的作用

registered 标志决定了 Handler 的初始化时机:

  • registered = false(Channel 还没注册)

    • 创建 PendingHandlerAddedTask,延迟到注册完成后执行
    • Initializer-A 就是这种情况
  • registered = true(Channel 已经注册)

    • 直接调用 callHandlerAdded0(),立即执行初始化
    • MyChannelInitializer 就是这种情况
4. ChannelInitializer 的自动移除机制

ChannelInitializer 在 initChannel(ChannelHandlerContext) 方法的 finally 块中会自动移除自己:

finally {
  ChannelPipeline pipeline = ctx.pipeline();
  if (pipeline.context(this) != null) {
      pipeline.remove(this);
  }
}

这确保了 ChannelInitializer 只执行一次,执行完就从 Pipeline 中消失。

5. 多态机制的应用

整个流程中,Java 的多态机制起到了关键作用:

// ChannelInitializer 中的代码
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
  // 调用抽象方法
  initChannel((C) ctx.channel());
}

虽然这里调用的是抽象方法 initChannel(Channel),但由于多态:

  • 如果当前实例是 Initializer-A,就调用 Initializer-A 重写的版本
  • 如果当前实例是 MyChannelInitializer,就调用 MyChannelInitializer 重写的版本

最终总结

通过本文的详细分析,我们完整地理解了 Netty Pipeline 的初始化流程。让我们用最简洁的方式总结一下核心要点:

整个流程的本质

Pipeline 初始化实际上是一个两阶段、两层 ChannelInitializer 的过程:

阶段一:准备阶段(Init)- “记账”
  • 创建 Initializer-A(匿名 ChannelInitializer)
  • 将 Initializer-A 添加到 Pipeline
  • 创建 PendingHandlerAddedTask,等待执行
阶段二:执行阶段(Register)- “结账”
  • 第一层初始化:执行 Initializer-A 的 initChannel()

    • 作用:将 MyChannelInitializer 添加到 Pipeline
    • 结果:Initializer-A 被移除,MyChannelInitializer 被添加
  • 第二层初始化:执行 MyChannelInitializer 的 initChannel()

    • 作用:执行用户的业务逻辑,添加业务 Handler
    • 结果:MyChannelInitializer 被移除,业务 Handler 留在 Pipeline

为什么要这样设计?

  1. 统一性:客户端和服务端使用相同的初始化流程
  2. 灵活性:通过 Initializer-A 作为中间层,可以在添加用户 Handler 前后做额外处理
  3. 安全性:延迟初始化确保在正确的时机(Channel 注册后)执行
  4. 清晰性:职责分离,Initializer-A 负责框架逻辑,MyChannelInitializer 负责业务逻辑

关键理解点

  1. Context 持有 Handlerhandler() 方法返回的是 Context 持有的 Handler 引用
  2. 多态机制:调用抽象方法时,会根据实际实例类型调用对应的重写版本
  3. registered 标志:决定是延迟执行还是立即执行
  4. 自动移除:ChannelInitializer 执行完后会自动从 Pipeline 中移除

Pipeline 状态变化总结

初始状态:
    head <-> tail

准备阶段后:
    head <-> ctx-A(Initializer-A) <-> tail
    [待处理任务队列:PendingHandlerAddedTask(ctx-A)]

第一层初始化后:
    head <-> ctx-B(MyChannelInitializer) <-> tail
    [Initializer-A 已移除]

第二层初始化后:
    head <-> [业务 Handler] <-> tail
    [MyChannelInitializer 已移除]

作者注:本文基于 Netty 4.x 版本源码分析,不同版本可能略有差异,但核心思想是一致的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值