Netty 中用了哪些设计模式?

#中间件#面试八股

Netty 中用了哪些设计模式?

结论先行:Netty 的代码几乎是一本设计模式教材:Reactor 模式撑起线程模型,责任链模式撑起 pipeline,工厂 + 建造者撑起启动流程,观察者撑起异步回调,装饰器与适配器撑起 Handler 体系,享元与对象池撑起内存管理。理解这些模式,比背 API 更能读懂源码。

一、模式与落点对照

模式在 Netty 中的位置作用
ReactorNioEventLoop + 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 的成本,也隐藏了释放资源的细节。
笔记加载中…