Netty框架中的@Skip使用说明

最近在学习Netty框架,对着教程上写了个简单的netty应用,可是死活调试不成功,对着程序跟教程上看了几遍也找不到原因,后来又重新写了一遍,服务端程序终于调试成功,原因出在了那个@Skip注释上了,代码如下:

package com.chris.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler.Skip;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.example.discard.DiscardServerHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.ReferenceCountUtil;

import java.net.SocketAddress;
import java.sql.Date;


/**
 * @author Chris
 * @date 2015-4-12
 */
public class NettyTimerServer {
	
	
	public void bind(int port) throws Exception{
		
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workGroup = new NioEventLoopGroup();
		
		try {
			ServerBootstrap b = new ServerBootstrap();
			b.group(bossGroup, workGroup)
			.channel(NioServerSocketChannel.class)
			.option(ChannelOption.SO_BACKLOG, 1024)
			.handler(new LoggingHandler(LogLevel.INFO))
			.childHandler(new ChildChannelHandler());
			System.out.println("server bind 8888");
			ChannelFuture f = b.bind(port).sync();
			System.out.println("finish bind");
			f.channel().closeFuture().sync();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			bossGroup.shutdownGracefully();
			workGroup.shutdownGracefully();
		}
		
		
	}
	
	private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{

		/* (non-Javadoc)
		 * @see io.netty.channel.ChannelInitializer#initChannel(io.netty.channel.Channel)
		 */
		@Override
		protected void initChannel(SocketChannel arg0) throws Exception {
			System.out.println("server initChannel");
			arg0.pipeline().addLast(new TimeServerHandler());
			//arg0.pipeline().addl
			
		}
		
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		try {
			new NettyTimerServer().bind(8888);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}

class TimeServerHandler extends ChannelHandlerAdapter {

	@Override
	@Skip
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		super.channelActive(ctx);
	}

	@Override
	@Skip
	public void channelRead(ChannelHandlerContext ctx, Object msg)
			throws Exception {
		ByteBuf buf = (ByteBuf)msg;
		
		byte[] bytes = new byte[buf.readableBytes()];
		
		buf.readBytes(bytes);
		
		String body = new String(bytes,"UTF-8");
		System.out.println("the server receive order:"+body);
		
		String currentTIme = "QUERY CURRENT TIME".equalsIgnoreCase(body)?(new Date(System.currentTimeMillis())).toString():"receive error order";
		
		ByteBuf resp = Unpooled.copiedBuffer(currentTIme.getBytes());
		
		ctx.write(resp);
	}

	@Override
	@Skip
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.flush();
	}

	@Override
	@Skip
	public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
			SocketAddress localAddress, ChannelPromise promise)
			throws Exception {
		// TODO Auto-generated method stub
		super.connect(ctx, remoteAddress, localAddress, promise);
	}
	
	
	
	
	
	
}

 

这个实现类的每个方法上都有一个@Skip注释,去掉注释之后,程序调试成功,使用netty开发的服务端程序可以正常接收和处理客户端连接。

被这个注释坑了一天了,于是特地去看了netty的源码,以下是关于@Skip源码的说明:

/**
     * Indicates that the annotated event handler method in {@link ChannelHandler} will not be invoked by
     * {@link ChannelPipeline}.  This annotation is only useful when your handler method implementation
     * only passes the event through to the next handler, like the following:
     *
     * <pre>
     * {@code @Skip}
     * {@code @Override}
     * public void channelActive({@link ChannelHandlerContext} ctx) {
     *     ctx.fireChannelActive(); // do nothing but passing through to the next handler
     * }
     * </pre>
     *
     * {@link #handlerAdded(ChannelHandlerContext)} and {@link #handlerRemoved(ChannelHandlerContext)} are not able to
     * pass the event through to the next handler, so they must do nothing when annotated.
     *
     * <pre>
     * {@code @Skip}
     * {@code @Override}
     * public void handlerAdded({@link ChannelHandlerContext} ctx) {
     *     // do nothing
     * }
     * </pre>
     *
     * <p>
     * Note that this annotation is not {@linkplain Inherited inherited}.  If you override a method annotated with
     * {@link Skip}, it will not be skipped anymore.  Similarly, you can override a method not annotated with
     * {@link Skip} and simply pass the event through to the next handler, which reverses the behavior of the
     * supertype.
     * </p>
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Skip {
        // no value
    }

大概意思就是说@Skip注释用来在实现了Handler的实现类中的方法上,程序运行过程中如果某个handler实现中的方法被@Skip注释了,则此方法不会被 ChannelPipeline 对象调用,所以,这就是为什么我的服务端程序死活调试不成功的原因。我们可以看看netty内部执行过程中是如何处理@Skip注释的,通过对源码文件全文扫苗,找到了对@Skip注释的处理都集中在了AbstractChannelHandlerContext中,下面贴出处理@Skip相关的方法源码:

/**
     * Returns an integer bitset that tells which handler methods were annotated with {@link Skip}.
     * It gets the value from {@link #skipFlagsCache} if an handler of the same type were queried before.
     * Otherwise, it delegates to {@link #skipFlags0(Class)} to get it.
     */
    static int skipFlags(ChannelHandler handler) {
        WeakHashMap<Class<?>, Integer> cache = skipFlagsCache.get();
        Class<? extends ChannelHandler> handlerType = handler.getClass();
        int flagsVal;
        Integer flags = cache.get(handlerType);
        if (flags != null) {
            flagsVal = flags;
        } else {
            flagsVal = skipFlags0(handlerType);
            cache.put(handlerType, Integer.valueOf(flagsVal));
        }

        return flagsVal;
    }

 

 /**
     * Determines the {@link #skipFlags} of the specified {@code handlerType} using the reflection API.
     */
    static int skipFlags0(Class<? extends ChannelHandler> handlerType) {
        int flags = 0;
        try {
            if (isSkippable(handlerType, "handlerAdded")) {
                flags |= MASK_HANDLER_ADDED;
            }
            if (isSkippable(handlerType, "handlerRemoved")) {
                flags |= MASK_HANDLER_REMOVED;
            }
            if (isSkippable(handlerType, "exceptionCaught", Throwable.class)) {
                flags |= MASK_EXCEPTION_CAUGHT;
            }
            if (isSkippable(handlerType, "channelRegistered")) {
                flags |= MASK_CHANNEL_REGISTERED;
            }
            if (isSkippable(handlerType, "channelUnregistered")) {
                flags |= MASK_CHANNEL_UNREGISTERED;
            }
            if (isSkippable(handlerType, "channelActive")) {
                flags |= MASK_CHANNEL_ACTIVE;
            }
            if (isSkippable(handlerType, "channelInactive")) {
                flags |= MASK_CHANNEL_INACTIVE;
            }
            if (isSkippable(handlerType, "channelRead", Object.class)) {
                flags |= MASK_CHANNEL_READ;
            }
            if (isSkippable(handlerType, "channelReadComplete")) {
                flags |= MASK_CHANNEL_READ_COMPLETE;
            }
            if (isSkippable(handlerType, "channelWritabilityChanged")) {
                flags |= MASK_CHANNEL_WRITABILITY_CHANGED;
            }
            if (isSkippable(handlerType, "userEventTriggered", Object.class)) {
                flags |= MASK_USER_EVENT_TRIGGERED;
            }
            if (isSkippable(handlerType, "bind", SocketAddress.class, ChannelPromise.class)) {
                flags |= MASK_BIND;
            }
            if (isSkippable(handlerType, "connect", SocketAddress.class, SocketAddress.class, ChannelPromise.class)) {
                flags |= MASK_CONNECT;
            }
            if (isSkippable(handlerType, "disconnect", ChannelPromise.class)) {
                flags |= MASK_DISCONNECT;
            }
            if (isSkippable(handlerType, "close", ChannelPromise.class)) {
                flags |= MASK_CLOSE;
            }
            if (isSkippable(handlerType, "deregister", ChannelPromise.class)) {
                flags |= MASK_DEREGISTER;
            }
            if (isSkippable(handlerType, "read")) {
                flags |= MASK_READ;
            }
            if (isSkippable(handlerType, "write", Object.class, ChannelPromise.class)) {
                flags |= MASK_WRITE;
            }
            if (isSkippable(handlerType, "flush")) {
                flags |= MASK_FLUSH;
            }
        } catch (Exception e) {
            // Should never reach here.
            PlatformDependent.throwException(e);
        }

        return flags;
    }

 

 @SuppressWarnings("rawtypes")
    private static boolean isSkippable(
            Class<?> handlerType, String methodName, Class<?>... paramTypes) throws Exception {

        Class[] newParamTypes = new Class[paramTypes.length + 1];
        newParamTypes[0] = ChannelHandlerContext.class;
        System.arraycopy(paramTypes, 0, newParamTypes, 1, paramTypes.length);

        return handlerType.getMethod(methodName, newParamTypes).isAnnotationPresent(Skip.class);
    }

 

相信不少netty初学者都会碰到此类问题吧,希望这篇文章能对大家有点帮助。

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: Netty框架中的@Skip使用说明

  • Trackback 关闭
  • 评论 (1)
    • nemo
    • 2021/04/02 2:08下午

    看了源码才知道这个注解的存在。。。为啥“不少初学者”会使用这个注解呢 🤦‍♂️

return top