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自动创建
关键点:
head和tail是 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 采用了一种**“延迟初始化”**的策略:
- 先把需要执行的任务记录下来(创建待处理任务)
- 等到合适的时机(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();
}
}
关键点理解:
-
b.handler(new MyChannelInitializer())- 这行代码只是把 MyChannelInitializer 保存到 Bootstrap 的配置中
- 此时还没有添加到 Pipeline
- 就像你把装修方案交给装修公司,但还没开始施工
-
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;
}
这个方法做了三件事:
| 步骤 | 方法调用 | 作用 | 类比 |
|---|---|---|---|
| 1 | channelFactory.newChannel() | 创建 Channel 实例 | 建房子打地基 |
| 2 | init(channel) | 初始化 Channel(准备阶段) | 确定装修方案 |
| 3 | register(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);
}
// ... 其他初始化逻辑
}
});
}
关键点理解:
-
创建了一个匿名 ChannelInitializer
- 我们称它为 Initializer-A(框架层的初始化器)
- 它的作用是:将用户的 Handler(MyChannelInitializer)添加到 Pipeline
- 这是 Netty 框架自己创建的,不是我们写的
-
调用了
p.addLast()- 将 Initializer-A 添加到 Pipeline 中
- 但是!Initializer-A 的
initChannel()方法还没有被调用 - 这就是"延迟初始化"的体现
-
此时 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 采用了一种巧妙的设计:
- 先记账:把需要执行的操作记录下来(创建任务)
- 后结账:等到 Channel 注册完成后,再统一执行这些任务
准备阶段小结:
到这里,准备阶段就完成了。此时的状态是:
- ✅ Initializer-A 已经被添加到 Pipeline 链表中
- ✅ 一个 PendingHandlerAddedTask 任务被创建并加入到待处理队列
- ❌ 但是 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() - 执行添加任务
让我们看看 PendingHandlerAddedTask 的 execute() 方法:
@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
为什么要这样设计?
- 统一性:客户端和服务端使用相同的初始化流程
- 灵活性:通过 Initializer-A 作为中间层,可以在添加用户 Handler 前后做额外处理
- 安全性:延迟初始化确保在正确的时机(Channel 注册后)执行
- 清晰性:职责分离,Initializer-A 负责框架逻辑,MyChannelInitializer 负责业务逻辑
关键理解点
- Context 持有 Handler:
handler()方法返回的是 Context 持有的 Handler 引用 - 多态机制:调用抽象方法时,会根据实际实例类型调用对应的重写版本
- registered 标志:决定是延迟执行还是立即执行
- 自动移除: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 版本源码分析,不同版本可能略有差异,但核心思想是一致的。
475

被折叠的 条评论
为什么被折叠?



