Netty 中用了哪些设计模式?
结论先行:Netty 的代码几乎是一本设计模式教材:Reactor 模式撑起线程模型,责任链模式撑起 pipeline,工厂 + 建造者撑起启动流程,观察者撑起异步回调,装饰器与适配器撑起 Handler 体系,享元与对象池撑起内存管理。理解这些模式,比背 API 更能读懂源码。
一、模式与落点对照
| 模式 | 在 Netty 中的位置 | 作用 |
|---|---|---|
| Reactor | NioEventLoop + Selector | 事件驱动、多路复用 |
| 责任链 | ChannelPipeline + ChannelHandler | 请求逐级处理、可插拔 |
| 建造者 | ServerBootstrap 的链式调用 | 复杂对象分步构建 |
| 工厂 | ChannelFactory、ReflectiveChannelFactory | 屏蔽 Channel 创建细节 |
| 观察者 | ChannelFutureListener | 异步结果通知 |
| 装饰器 | WrappedByteBuf、ChannelDuplexHandler | 在不改原类的前提下增强 |
| 适配器 | ChannelInboundHandlerAdapter | 空实现,按需覆盖 |
| 策略 | EventExecutorChooser、ByteBufAllocator | 运行时替换算法 |
| 享元 | PooledByteBufAllocator、Recycler | 复用对象,降低开销 |
| 单例 | @Sharable Handler、Unpooled 工具类 | 全局唯一实例 |
| 模板方法 | SimpleChannelInboundHandler | 固定骨架,子类填内容 |
二、责任链:pipeline 的灵魂
Inbound 事件(读)──────────────────────────────>
[Decoder] → [IdleState] → [Auth] → [Business]
│
Outbound 事件(写)<───────────────────────────┘
[Encoder] ← [Compress] ← [Business.write]
ch.pipeline()
.addLast("frame", new LengthFieldBasedFrameDecoder(65535, 0, 4, 0, 4))
.addLast("codec", new MessageCodec())
.addLast("idle", new IdleStateHandler(60, 30, 0))
.addLast("auth", new AuthHandler())
.addLast("biz", new BusinessHandler());
每个 Handler 只关心自己那一步,ctx.fireChannelRead(msg) 把事件传给下一个,ctx.write(msg) 则反向走 Outbound 链。
三、观察者 + Future:异步结果
ChannelFuture f = channel.writeAndFlush(resp);
f.addListener((ChannelFutureListener) future -> {
if (!future.isSuccess()) {
log.error("write failed", future.cause());
} else {
metrics.incr("write.ok");
}
});
ChannelFuture 是观察者模式的载体:调用方注册监听器,IO 完成时由 EventLoop 线程回调,避免阻塞等待。
四、建造者:为什么启动要链式
new ServerBootstrap()
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(initializer)
.bind(8080);
ServerBootstrap 有十几个可选参数,构造器无法表达“可选 + 有默认值 + 校验顺序”,建造者把装配与使用分离,也让 validate() 能在 bind 前统一做参数检查。
五、适配器与模板方法
// 适配器:只关心自己需要的方法
public class MyHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 只覆盖读,其余方法用空实现
ctx.fireChannelRead(msg);
}
}
// 模板方法:父类管释放,子类只管业务
public class BizHandler extends SimpleChannelInboundHandler<Request> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Request req) {
// 不用管 ReferenceCountUtil.release
}
}
SimpleChannelInboundHandler 在 channelRead 里调用完 channelRead0 后自动释放消息,这就是模板方法的典型收益:把易漏的固定步骤固化在父类。
常见追问
- 追问:为什么 Handler 默认不能加
@Sharable?Handler 实例可能持有与连接相关的状态,多 Channel 共享会串数据;无状态才可标注。 - 追问:pipeline 的顺序重要吗?非常重要。解码器必须在业务 Handler 之前,编码器在写路径上必须位于业务之后。
- 追问:
ChannelHandlerAdapter和ChannelInboundHandlerAdapter的区别?前者是最基础的适配器,后者专门适配入站接口,还提供ctx.fireChannelRead的默认转发。
小结
- Reactor 定线程模型,责任链定处理流程,两者是 Netty 的骨架。
- 建造者管装配,工厂管创建,策略管可替换算法。
- 观察者(Future + Listener)是异步编程的落点。
- 适配器与模板方法降低了写 Handler 的成本,也隐藏了释放资源的细节。