米哈游1面:Netty 采用了哪些经典的设计模式?

Netty 是一个优秀的、高性能、异步的事件驱动网络应用框架,它内部使用了许多经典的设计模式。这篇文章,我们来详细分析 Netty到底使用了哪些优秀的设计模式,并且结合 Netty 的具体实现来探讨这些模式的应用。

这篇文章,我们分析 8个有代表性的模式:

  1. 责任链模式
  2. 观察者模式
  3. 工厂模式
  4. 适配器模式
  5. 策略模式
  6. 原型模式
  7. 单例模式
  8. 模板方法模式

1. 责任链模式

概念:

责任链模式(Chain of Responsibility)用于将请求沿着处理链传播,每个对象都有机会处理请求或将其传递给下一个对象。

Netty 中的应用:

Netty 的 ChannelPipelineChannelHandler 正是责任链模式的经典实现。ChannelPipeline 是一组互相连接的 ChannelHandler 对象,每个 ChannelHandler 执行对数据流的处理。

实现案例:

  • 在 Netty 中,ChannelPipeline 提供了一组按顺序工作的 ChannelHandler,可分为入站(inbound)和出站(outbound)。
  • 当接收到数据时,它会沿入站处理链传播,各个入站 ChannelHandler 依次处理该数据(如解码、业务逻辑处理等)。
  • 当发送数据时,它会沿出站处理链传播,各个出站 ChannelHandler 依次处理该数据(如编码、压缩等)。

    代码示例:

ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new DecoderHandler());
pipeline.addLast(new BusinessLogicHandler());
pipeline.addLast(new EncoderHandler());

每个处理器都会处理其关心的部分,并将其余的事情交给链内的下一个处理器。

2. 观察者模式

概念:

观察者模式(Observer)定义了对象之间的一对多依赖关系,当目标对象状态发生改变时,其依赖者(观察者)会收到通知并自动更新。

Netty 中的应用:

Netty 的事件驱动模型通过观察者模式实现。当 Selector 检测到特定事件(如 readwrite 准备完成)后会通知对应的 ChannelChannel 会触发事件并将任务提交到合适的处理器执行。

实现案例:

  • ChannelFuture 是 Netty 中观察者模式的典型应用,例如,当你向服务器发送数据时,可以通过 ChannelFuture 注册监听器,来监控数据发送是否完成。
  • 当操作完成时,监听器会被通知从而执行用户定义的回调逻辑。

    代码示例:

ChannelFuture future = channel.writeAndFlush(message);
future.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
        if (future.isSuccess()) {
            System.out.println("Write successful!");
        } else {
            System.out.println("Write failed: " + future.cause());
        }
    }
});

ChannelFutureListener 是典型的观察者,当 ChannelFuture 的状态变化时会收到通知。

3. 工厂模式

概念:

工厂模式(Factory)用于创建对象的实例,屏蔽了对象创建的复杂性。

Netty 中的应用:

Netty 使用工厂模式隐藏了创建复杂对象的细节,常见的是 EventLoopGroupBootstrap 等组件。

实现案例:

  • EventLoopGroup 是执行事件循环的关键组件,Netty 提供了多种实现(如 NIO 的 NioEventLoopGroup 和 Epoll 的 EpollEventLoopGroup),用户可以通过抽象工厂模式指定自己需要的实现。
  • BootstrapServerBootstrap 也是工厂模式的经典应用,它们用于构造客户端和服务端配置。

    代码示例:

EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
    .channel(NioSocketChannel.class)
    .handler(new ChannelInitializer<SocketChannel>() {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline().addLast(new MyHandler());
        }
    });

用户只需要调用 Bootstrap 的方法即可完成工厂的配置,隐藏了复杂的配置逻辑。

4. 适配器模式

概念:

适配器模式(Adapter)用来将一个类的接口转换为另一个接口,以实现接口之间的兼容。

Netty 中的应用:

Netty 的 ChannelHandlerAdapterChannelInboundHandlerAdapter 是典型的适配器模式应用,它们简化了 ChannelHandler 的实现。

实现案例:

  • Netty 的 ChannelHandler 提供了很多接口方法,但用户可能只需要实现一小部分逻辑。在这种情况下,用户无需全部实现所有方法,可以继承 ChannelInboundHandlerAdapterChannelOutboundHandlerAdapter 来简化代码。

    代码示例:

public class MyHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("Message received: " + msg);
        ctx.fireChannelRead(msg);
    }
}

通过适配器,用户不需要实现 ChannelHandler 的所有方法,同时保留了灵活性。

5. 策略模式

概念:

策略模式(Strategy)将一组算法封装起来,使得它们可以互换,同时将算法的选择独立于使用这些算法的客户。

Netty 中的应用:

Netty 在其 EventLoopGroup 和处理 IO 的任务分配中采用了策略模式。通过抽象的 EventLoop,Netty 支持多种不同的多路复用机制(如 NIO、Epoll 等)。

实现案例:

  • EventLoopGroup 支持多种实现,并根据运行环境动态选择策略,例如在 Linux 平台优先选择 Epoll。
  • Netty 的序列化与解码器也使用了策略模式,不同的序列化方式可以互相替换(如 protobuf、JSON 等)。

    代码示例:

EventLoopGroup group = new EpollEventLoopGroup(); // Linux 平台下的高性能实现
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
    .channel(EpollSocketChannel.class);

用户可以灵活替换实现以适配特定需求。

6. 原型模式

概念:

原型模式 (Prototype)通过克隆的方式创建对象,而不是直接实例化。

Netty 中的应用:

Netty 的缓冲区分配(ByteBufAllocator)中使用了原型模式。为了减少内存分配和回收的开销,Netty 提供了池化的缓冲区,通过克隆和回收来重复利用缓冲区。

实现案例:

  • PooledByteBufAllocator 负责提供缓冲区,其内部维护了一系列固定大小的内存块,用于内存分配和回收。
  • 使用原型模式减少了频繁的内存分配成本。

7. 单例模式

概念:

单例模式(Singleton)保证一个类只存在一个实例,并提供全局访问点。

Netty 中的应用:

Netty 中某些共享的组件采用单例模式,例如 Unpooled 类和一些内部工具类。

实现案例:

  • Unpooled 是非池化缓冲区的工厂类,它使用单例模式提供缓冲区操作的统一入口。

    代码示例:

ByteBuf buf = Unpooled.buffer(256);

8. 模板方法模式

概念:

模板方法模式(Template Method)允许在基类定义操作的框架,而将具体实现延迟到子类。

Netty 中的应用:

Netty 的很多组件都提供了模板方法模式的实现,例如 ChannelInitializer 用于设置 ChannelPipeline

实现案例:

  • 用户通过继承 ChannelInitializer 定义自己的逻辑,而底层框架负责调用和执行。

    代码示例:

public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(new MyHandler());
    }
}

9. 总结

这篇文章,我们详细地分析了 Netty 包含的经典设计模式,并结合 Netty 的具体实现来探讨这些模式的应用。因为篇幅有限,我们只分析了 8种有代表性的模型,但是 Netty的设计模式绝不仅仅只有这些,它们都是经典的设计模式。作为Java领域一款经典的网络通信工具,Netty绝对值得我们花时间去琢磨。

1