乐于分享
好东西不私藏

Netty 源码深度解析:为什么它是 Java 世界里面向对象设计的“巅峰之作”?

Netty 源码深度解析:为什么它是 Java 世界里面向对象设计的“巅峰之作”?

前言

在很多开发者的刻板印象里,“高性能”和“面向对象”是鱼与熊掌不可兼得。为了极致的速度,代码就应该写的像“天书”一样。然而,Netty 彻底打破了这种刻板印象。

作为一个单机能支撑百万并发的底层通信框架,Netty 的源码简直就是一本“设计模型实战手册”。它不仅没有因为追求性能而放弃对象模型,反而通过精妙的对象抽象,解决了网络编程中最让人头疼的复杂性问题。

今天,让我们来拆解 Netty 的核心源码,看看什么是真正的“工业级面向对象”。

它不是“操作 Socket”,而是“和对象对话”

之前文章中提到:不要围绕数据写代码,而是围绕对象写代码

Netty 在最底层就贯彻了这一点。

例如:

if (key.isReadable()) {
    SOcketChannel
 channel = (SocketChannel) key.channel();
    int
 len = channel.read(buffer);
    // ...

}

这就是典型的恶过程式思维。你在不断地问:“现在能不能读?”,“channel 是什么类型?”。

channel.pipeline().fireChannelRead(msg);

Netty 几乎从不让你判断状态然后自己处理,而是把“事件”封装成对象,把“行为”交给对象自己处理。你只需要告诉系统:“当数据来的时候,请执行这个对象的这个逻辑”。至于现在 Socket 是什么状态,那是对象自己要操心的事情,不是你的事。

Channel / Pipeline / Handler:职责边界的艺术

之前在文章中,批判过一种代码:

Service 变成了上帝类,什么都要知道,什么都要管

而且很多人对这种批判嗤之以鼻。

Netty 的设计,几乎是反 Service 上帝类的典范。

Channel:“连接”概念的对象化

Netty 首先将”连接”这个概念,进行了封装,封装为一个 Channel。它代表了一次“连接的生命周期”。

它是一个完整的对象,知道自己是否 active,知道什么时候可以 write。你不再需要写这种代码:

if (channel.isActive()) {
    if
 (channel.isWritable()) {
        // ...

    }
}

而是只需要:

channel.writeAndFlush(msg);

“能不能写?”,“什么时候写?”,“写失败怎么办?”,这都是 Channel 自己的事情,不是调用者的事。

正是之前文章中写过的一句话:

把行为还给对象,让它对自己负责。

Pipeline:逻辑的流水线

封装好“连接”之后,接下来要处理的是数据逻辑。如果用传统的方式编写网络框架,代码往往会退化为“高级 C 语言”风格:

if (isHttp) {
    decodeHttp();
    if
 (isAuth) {
        if
 (isBusiness) {
            doBiz();
        }
    } else {
        auth();
    }
}

Netty 用 ChannelPipeline 把这件事彻底对象化了,从而终结了这种混乱。

pipeline
    .addLast(new HttpDecoder())
    .addLast(new AuthHandler())
    .addLast(new BizHandler());

Netty 首先把“网络请求处理”抽象成了一个 ChannelPipeline,它像是一条工业流水线,上面挂满了 ChannelHandler

每一个 Handler 都是一个独立的对象,且只关心一件事,只处理自己负责的那一小段逻辑,有的只负责把 ByteBuf 转成 String,有的只负责心跳……他们无需知道前后是谁,也无需关心整体流程。

Handler 只承载具体规则,具体的编排由 Pipeline 来实现,这种设计让功能的增删变成了“拔插积木”,而不是在“屎山”里动手术。

EventLoop:把并发“关进对象里”

前面文章有提到:String 和 Integer 在系统中裸奔,是很多混乱的根源。这里的“基础类型裸奔”,其实不只是单指 String / int,而是更广义的:没有语义约束、没有行为、只有能力的东西,被到处传来传去。

而在并发的领域,这个“裸奔”的就是 ThreadRunnableExecutorService。想一下平时多线程的代码,经常需要考虑几个问题:“这个任务属于谁?”、“它会不会和别的任务并发执行?”、“有没有线程安全问题?”、“需要加锁吗?”,这些问题,全都压在了写代码人的脑子里。

EventLoop 的出现,本质上是 Netty 为了消除这种“并发恐惧”而做出的对象建模。

它既不是线程,也不是“线程池”,而是一个“负责串行执行任务的抽象对象”。

作为使用者,不需要关心“哪个线程”、“什么时候切换”、“有没有锁”,只需要关心一件事:“把这个任务交给负责这个 Channel 的 EventLoop”。

EventLoop 的设计,有几个非常强的约束:

  • • 由框架统一管理,不可随机创建
  • • 严格绑定,不可随意切换
  • • 一个 Channel 终生只绑定了一个 EventLoop。

这意味着,一个 Channel 的所有 I/O 事件和 Handler 回调永远在同一个 EventLoop 上串行执行,所以,在 Handler 中,默认不需要写任何并发控制的代码。

并发问题在 Netty 中不是被“管理”的,而是从设计层面被“消灭”的,这不是线程池能做到的事情,是对象建模的胜利。

Netty 为什么几乎没有“万能对象”?

Netty 的类库极其庞大,但你会发现,它几乎没有一个对象是“想干所有事情”的。

同样是“上下文”,Netty 拆出了多个职责明确的对象:Channel(连接)、ChannelHandlerContext(当前 handler 的上下文)、ByteBuf(数据容器)。

每个对象的使用场景都极其明确,没有一个对象想“覆盖所有场景”,用对象边界,限制使用者的自由,换取系统的可维护性。总有人说面向对象会造成系统混乱,在大多数场景下,完全是一种开发人员抽象设计能力缺陷的甩锅行为。

Netty 有一个隐形的原则:“错误的代码很难写出来”。高级的设计,是靠对象边界来约束人的行为,从而换取系统的长期可维护性

结语

Netty 的设计告诉我们,面向对象从来不是为了装酷,也不是为了增加那一两层的调用开销。它存在的唯一目的,是为了使复杂性受控。Netty 从不要求你“理解整个系统”,只是要求你“把该做的事情交给该负责的对象”。

当你面对的是单机百万连接、是极其复杂的网络协议解析、是瞬息万变的并发状态时,过程式的思维会迅速枯竭,把你拖入“修不完的 Bug”和“看不懂的逻辑”中。而一套清晰的对象模型,能让你在惊涛骇浪的底层开发中,依然像在写简单的业务逻辑一样优雅。

面向对象设计,不是为了让简单的事情变复杂,而是为了让复杂的事情,在我们的脑子里变简单。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Netty 源码深度解析:为什么它是 Java 世界里面向对象设计的“巅峰之作”?

评论 抢沙发

3 + 4 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮