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 之后该方法会被调用;