前言:
之前分析使用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实例,避免创建大量对象。