15-ChannelInitializer

ChannelInitializer

  • ChannelInitializer 是一种特殊的 ChannelHandler,它也是一种 ChannelInboundHandler,它提供了在通道注册到 EventLoop 后初始化通道的简单方法;其主要目的是在某个 Channel 注册到 EventLoop 后,对这个 Channel 执行一些初始化操作。在初始化完成之后,ChannelInitializer 会 将自己从 Pipeline 中移除,不会影响后续的操作。

  • 作用:在某个Channel注册到EventLoop后,对这个Channel执行一些初始化操作,初始化操作完成后会将自身从Pipeline中移除。

一、继承关系

在这里插入图片描述

二、源码

2.1 注释

  • 源码注释如下:
/**
 * 一个特殊的ChannelInboundHandler,提供了一个便捷的方式在Channel注册到EventLoop的时候来初始化这个Channel
 * A special {@link ChannelInboundHandler} which offers an easy way to initialize a {@link Channel} once it was
 * registered to its {@link EventLoop}.
 *
 * 很多时候是在 Bootstrap#handler/ServerBootstrap#handler或者ServerBootstrap#childHandler 方法中使用,
 * 用于初始化 Channel 的 ChannelPipeline
 * 
 * Implementations are most often used in the context of {@link Bootstrap#handler(ChannelHandler)} ,
 * {@link ServerBootstrap#handler(ChannelHandler)} and {@link ServerBootstrap#childHandler(ChannelHandler)} to
 * setup the {@link ChannelPipeline} of a {@link Channel}.
 *
 * <pre>
 * 使用示例如下:
 * public class MyChannelInitializer extends {@link ChannelInitializer} {
 *     public void initChannel({@link Channel} channel) {
 *         channel.pipeline().addLast("myHandler", new MyHandler());
 *     }
 * }
 *
 * {@link ServerBootstrap} bootstrap = ...;
 * ...
 * bootstrap.childHandler(new MyChannelInitializer());
 * ...
 * </pre>
 * 注意到该类是由 @Sharable 标注的,因此实现必须是线程安全的,能够复用
 * Be aware that this class is marked as {@link Sharable} and so the implementation must be safe to be re-used.
 */

2.2 源码分析

  • initChannel 方法是我们需要自行实现的
 /**
     * This method will be called once the {@link Channel} was registered. After the method returns this instance
     * will be removed from the {@link ChannelPipeline} of the {@link Channel}.
     * <p>
     * Channel被注册到EventLoop的时候initChannel会被调用,ChannelInitializer实现类必须重写该方法。
     * 并且该方法调用返回之后,ChannelInitializer实例会从ChannelPipeline移除
     *
     * @param ch the {@link Channel} which was registered.
     * @throws Exception is thrown if an error occurs. In that case it will be handled by
     *                   {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close
     *                   the {@link Channel}.
     */
    protected abstract void initChannel(C ch) throws Exception;
  • initChannel(ChannelHandlerContext ctx) 初始化,执行handler的添加逻辑
 @SuppressWarnings("unchecked")
    private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
        // Guard against re-entrance. 解决并发问题,原来没有放进去的话会返回null,原来有就不会放进去,返回旧值
        if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) {
            try {
                //1.初始化通道,调用用户自己的实现添加 handler
                initChannel((C) ctx.channel());
            } catch (Throwable cause) {
                //2.发生异常时,执行异常处理,记录日志并关闭 ChannelHandlerContext
                // Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).
                // We do so to prevent multiple calls to initChannel(...).
                exceptionCaught(ctx, cause);
            } finally {
                //3.从 pipeline 移除 ChannelInitializer 自身(这行日志是我自己添加的)
                logger.info("initChannel移除: " + ctx
                        + ", 内部的handler是: " + ctx.handler()
                        + ", handler类型是: " + ctx.handler().getClass()
                        + ", ctx 类型是: " + ctx.getClass());
                remove(ctx);
            }
            //4.初始化成功
            return true;
        }
        //5.初始化失败
        return false;
    }
  • 其他代码
@Sharable
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelInitializer.class);

    // We use a ConcurrentMap as a ChannelInitializer is usually shared between all Channels in a Bootstrap /
    // ServerBootstrap. This way we can reduce the memory usage compared to use Attributes.
    /**
     * 由于 ChannelInitializer 通常可以在所有的 Bootstrap/ServerBootstrap 通道中共享,因此我们用一个 ConcurrentMap
     * 这种方式相对于使用 Attributes 方式,可以减少内存的使用,相当于不同的通道,对应的 ChannelHandlerContext 不同,由
     * ConcurrentMap 来解决了并发问题
     */
    private final ConcurrentMap<ChannelHandlerContext, Boolean> initMap = PlatformDependent.newConcurrentHashMap();

    /**
     * Handle the {@link Throwable} by logging and closing the {@link Channel}. Sub-classes may override this.
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (logger.isWarnEnabled()) {
            logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), cause);
        }
        ctx.close();
    }

    private void remove(ChannelHandlerContext ctx) {
        try {
            ChannelPipeline pipeline = ctx.pipeline();

            //1.从 pipeline 找到 ChannelHandler 对应的 ChannelHandlerContext 节点
            //(pipeline 中的节点是ChannelHandlerContext,ChannelHandlerContext包装了ChannelHandler)
            if (pipeline.context(this) != null) {
                //2.移除对应的handler(ChannelInitializer也是一种handler,内部会把 handler 包
                // 装成的ChannelHandlerContext 节点,再删除节点,pipeline内部是链表结构,节点
                // 是ChannelHandlerContext类型)
                pipeline.remove(this);
            }
        } finally {
            //3.从initMap移除
            initMap.remove(ctx);
        }
    }
}

2.3 handlerAdded和channelRegistered

  • handlerAdded 内部会判断 Channel 是否已经注册,注册的话会调用 initChannel 来执行自定义的handler 添加逻辑;
  • 在 Channel 注册到 EventLoop 上后,会触发 Channel Registered 事件。那么 ChannelInitializer 的 channelRegistered(ChannelHandlerContext ctx) 方法会得到执行,内部调用 initChannel 方法
  • 二者代码如下:

    /**
     * {@inheritDoc} If override this method ensure you call super!
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //1.已注册
        if (ctx.channel().isRegistered()) {
            //添加该行日志调试
            logger.info("ChannelInitializer 的 handlerAdded 方法执行... " );
            // This should always be true with our current DefaultChannelPipeline implementation.
            // The good thing about calling initChannel(...) in handlerAdded(...) is that there will be no ordering
            // surprises if a ChannelInitializer will add another ChannelInitializer. This is as all handlers
            // will be added in the expected order.
            initChannel(ctx);
        }
    }
    
     @Override
    @SuppressWarnings("unchecked")
    public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        // Normally this method will never be called as handlerAdded(...) should call initChannel(...) and remove
        // the handler.
        //添加该行日志调试
        logger.info("进入 ChannelInitializer 的 channelRegistered 方法... " );
        // 初始化 Channel
        if (initChannel(ctx)) {
            //添加该行日志调试
            logger.info("ChannelInitializer 的 channelRegistered 方法进入if逻辑执行... " );
            // we called initChannel(...) so we need to call now pipeline.fireChannelRegistered() to ensure we not
            // miss an event.
            // 重新触发 Channel Registered 事件
            ctx.pipeline().fireChannelRegistered();
        } else {
            // 继续向下一个节点传播Channel Registered 事件
            // Called initChannel(...) before which is the expected behavior, so just forward the event.
            ctx.fireChannelRegistered();
        }
    }
  • 换言之,用户自定义的 ChannelInitializer 初始化逻辑,看会被这两个方法调用执行,结合添加了日志后调试情况和查资料来看,几乎都是 handlerAdded 方法调用的,不知道什么情况下会由 channelRegistered 调用执行 initChannel(C ch) ;
  • 具体调用逻辑,按照调用栈可以回到 AbstractChannel.AbstractUnsafe#register0 方法,如下:
private void register0(ChannelPromise promise) {
             // ... 其他逻辑
                 
            pipeline.invokeHandlerAddedIfNeeded();

            //7.回调通知promise执行成功
            safeSetSuccess(promise);

            //8.触发通知已注册事件
            pipeline.fireChannelRegistered();

             // ... 其他逻辑
        }

三、使用示例

  • ChannelInitializer的 initChannel 抽象方法在 Channel 被注册到 EventLoop 的时候会被调用,ChannelInitializer 实现类必须重写该方法。

  • 如下是使用代码

    //启动器中设置 ChannelInitializer
  .childHandler(new MyChannelInitializer());
  
  //自定义的ChannelInitializer
    public class MyChannelInitializer extends ChannelInitializer {

    @Override
    protected void initChannel(Channel ch) throws Exception {
            System.out.println("MyChannelInitializer begin ... ");
            ch.pipeline().addLast("logging",new LoggingHandler(LogLevel.INFO));
            ch.pipeline().addLast(new EchoServerHandler());
    }
}
  • 前面提到该方法调用是在 Channel 被注册到 EventLoop 的时候调用,因此运行服务端之后,启动客户端,连接成功之后就会触发方法的调用,下面的代码打印示例:
//server端日志

Connected to the target VM, address: '127.0.0.1:65446', transport: 'socket'
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/intellif/.m2/repository/org/slf4j/slf4j-simple/1.7.25/slf4j-simple-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/intellif/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.7/log4j-slf4j-impl-2.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]
Thread[nioEventLoopGroup-2-1,10,main]: register
Thread[nioEventLoopGroup-2-1,10,main]: user handler
[nioEventLoopGroup-2-1] INFO io.netty.channel.ChannelInitializer - ChannelInitializer 的 handlerAdded 方法执行... 
[nioEventLoopGroup-2-1] INFO io.netty.channel.ChannelInitializer - initChannel移除: ChannelHandlerContext(ServerBootstrap$1#0, [id: 0x024ee434]), 内部的handler是: io.netty.bootstrap.ServerBootstrap$1@5a308693, handler类型是: class io.netty.bootstrap.ServerBootstrap$1, ctx 类型是: class io.netty.channel.DefaultChannelHandlerContext
Thread[nioEventLoopGroup-2-1,10,main]: PendingRegistrationPromise
Thread[nioEventLoopGroup-2-1,10,main]: ServerBootstrapAcceptor
Thread[nioEventLoopGroup-2-1,10,main]: bind

[nioEventLoopGroup-2-2] INFO io.netty.channel.ChannelInitializer - ChannelInitializer 的 handlerAdded 方法执行... 
Thread[nioEventLoopGroup-2-2,10,main]: register   //这里客户端连接成功
MyChannelInitializer begin ...  // MyChannelInitializer 执行,后面会移除 MyChannelInitializer
EchoServerHandler 构造方法执行 ...  //添加自定义的handler
[nioEventLoopGroup-2-2] INFO io.netty.channel.ChannelInitializer - initChannel移除: ChannelHandlerContext(MyChannelInitializer#0, [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196]), 内部的handler是: com.intellif.channelinitializer.MyChannelInitializer@332f2ee9, handler类型是: class
com.intellif.channelinitializer.MyChannelInitializer, ctx 类型是: class io.netty.channel.DefaultChannelHandlerContext
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] REGISTERED
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] ACTIVE
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] READ: 12B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 4e 65 74 74 79 20 72 6f 63 6b 73 21             |Netty rocks!    |
+--------+-------------------------------------------------+----------------+
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] WRITE: 12B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 4e 65 74 74 79 20 72 6f 63 6b 73 21             |Netty rocks!    |
+--------+-------------------------------------------------+----------------+
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] READ COMPLETE
Server received: Netty rocks!
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] WRITE: 0B
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] FLUSH
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] CLOSE
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 ! R:/127.0.0.1:49196] INACTIVE
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 ! R:/127.0.0.1:49196] UNREGISTERED
  • 本文主要关于 ChannelInitializer 的用法,主要是通过重写 initChannel 函数来添加需要的 handler 处理器,当当前的 channel 被注册到 Eventloop 之后该方法会被调用;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值