前言:

之前分析使用ChannelHandler的文章中,每次我们在ChannelPipeline中添加上新new出来的ChannelHandler即可。在ChannelPipeline.addFirst()或其他相关添加方法中,我们可以看到以下代码

public class DefaultChannelPipeline implements ChannelPipeline {
    @Override
    public final ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            // 在这里会验证一下
            checkMultiplicity(handler);
            name = filterName(name, handler);

            newCtx = newContext(group, name, handler);

            addFirst0(newCtx);
            ...
        }
    }
    
    // 校验是否重复添加
    private static void checkMultiplicity(ChannelHandler handler) {
        if (handler instanceof ChannelHandlerAdapter) {
            ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
            // 如果ChannelHandler非sharable且已经添加过,则直接抛出异常
            if (!h.isSharable() && h.added) {
                throw new ChannelPipelineException(
                        h.getClass().getName() +
                        " is not a @Sharable handler, so can't be added or removed multiple times.");
            }
            h.added = true;
        }
    }
}

在这里可以看到,如果ChannelHandler非sharable类型,且已经被添加过,则直接抛错。

在这里引出一个sharable类型,这个是做什么的呢?貌似平时根本没有用到过呢。本文就来分析下这个sharable类型。

1.ChannelHandler的sharable类型判断

我们通过代码来看下sharable是如何判断的

public abstract class ChannelHandlerAdapter implements ChannelHandler {
	public boolean isSharable() {
        Class<?> clazz = getClass();
        Map<Class<?>, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
        Boolean sharable = cache.get(clazz);
        if (sharable == null) {
            // 判断当前ChannelHandler是否有@Sharable注解
            sharable = clazz.isAnnotationPresent(Sharable.class);
            cache.put(clazz, sharable);
        }
        return sharable;
    }

主要就是查看这个ChannelHandler是否有@Sharable注解,判断方式还是比较简单的。

2.@Sharable注解

/**
     * Indicates that the same instance of the annotated {@link ChannelHandler}
     * can be added to one or more {@link ChannelPipeline}s multiple times
     * without a race condition.
     * <p>
     * If this annotation is not specified, you have to create a new handler
     * instance every time you add it to a pipeline because it has unshared
     * state such as member variables.
     * <p>
     * This annotation is provided for documentation purpose, just like
     * <a href="http://www.javaconcurrencyinpractice.com/annotations/doc/">the JCIP annotations</a>.
     */
    @Inherited
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Sharable {
        // no value
    }

注解相当优秀,我们简单来翻译下:

1.当ChannelHandler类上有@Sharable注解时,那么这个ChannelHandler的实例可以被添加到多个ChannelPipeline中

2.如果一个ChannelHandler类上没有@Sharable注解时,只能在每个ChannelPipeline中创建新的ChannelHandler实例

3.@Sharable注解的使用场景

通过类注释我们基本了解了@Sharable注解的适用场景。那么问题来了,什么样的ChannelHandler实例是可以被添加到多个ChannelPipeline呢?

1)如果我们期望进行某方面的统计,那么是需要一个公用ChannelHandler实例

2)如果某一个ChannelHandler可以被作为一个工具类,那么为了节省内存(避免创建大量的同类型ChannelHandler),是可以考虑使用同一个实例

3.1 工具类类型的ChannelHandler

在Netty源码中搜索@Sharable,我们可以看到,有很多ChannelHandler添加了该注解,我们挑个典型StringEncoder

@Sharable
public class StringEncoder extends MessageToMessageEncoder<CharSequence> {
    private final Charset charset;

    // 指定默认的编码
    public StringEncoder() {
        this(Charset.defaultCharset());
    }

    public StringEncoder(Charset charset) {
        this.charset = ObjectUtil.checkNotNull(charset, "charset");
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception {
        if (msg.length() == 0) {
            return;
        }

        out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), charset));
    }
}

一般来说,我们进行String编码时,可以当做一个通用需求,这时可以考虑使用同一个StringEncoder实例。

不仅这个,还有很多其他,比如Base64Decoder、Base64Encoder、ProtobufDecoder、ProtobufEncoder等。

3.2 统计类型的ChannelHandler

我们来看一个Netty提供的测试示例

@Sharable
    static class LocalHandler extends ChannelInboundHandlerAdapter {
        private final String name;

        public volatile ChannelFuture lastWriteFuture;

        public final AtomicInteger count = new AtomicInteger(0);

        LocalHandler(String name) {
            this.name = name;
        }

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            for (int i = 0; i < messageCountPerRun; i ++) {
                lastWriteFuture = ctx.channel().write(name + ' ' + i);
            }
            ctx.channel().flush();
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            // 当发生read动作时,将count++
            count.incrementAndGet();
            ReferenceCountUtil.release(msg);
        }
    }

示例比较简单,就是在当前ChannelHandler发生read动作时,将计数器 count加一

总结:

以后我们在使用ChannelHandler时,如果符合以上两种类型的,尽量选择公用ChannelHandler实例,避免创建大量对象。