java中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初学者都会碰到此类问题吧,希望这篇文章能对大家有点帮助。

时间: 2024-07-30 19:20:33

java中Netty框架中的@Skip使用详解的相关文章

PHP中MVC框架之文件入口实例详解

MVC的文件入口怎么写,完全是根据程序员的引擎设计来做,也是根据程序员喜好来做,但我们的最终目的却是通过简单的代码引入引擎来处理其它的事务,就像我们要开车一样,我们首先要点火,发动机才能发动一样.在写入口之前我们需要考虑几个方面,URL解析方式,需要哪些用户参数或系统需要带入,用户参数需要更改的地方,我们需要统一用一个文件来引入,文件可以是xml,也可以的PHP,也可以是其它的想到的方法,但keheng这里用的却是php里面数组形式引入,这种形式貌似写缓存的形式,从数据库读取了数据再生成一个ph

详解Java的Hibernat框架中的Map映射与SortedMap映射_java

Map映射Map映射是一个java集合存储在键 - 值对的元素,并且不允许在列表中重复的元素. Map接口提供三种collection视图,允许Map内容看作是一组键-值集合,或者设置键 - 值映射关系. Map被映射到映射表中一个<map>元素和无序的地图可以在java.util.HashMap中被初始化. 定义RDBMS表: 考虑一个情况,我们需要员工记录存储在EMPLOYEE表,将有以下结构: create table EMPLOYEE ( id INT NOT NULL auto_in

详解Java的Hibernate框架中的set映射集与SortedSet映射_java

Set集合Set是一个java集合不包含任何重复的元素.更正式地说,Set不包含任何元素对e1和e2,使得e1.equals(e2),和至多一个空元素.所以被添加到一组对象必须实现equals()和hashCode()方法,使Java可以判断任何两个元素/对象是否是相同的. 集被映射到与映射表中<set>元素,并在java.util.HashSet中初始化.可以使用Set集合在类时,有一个集合中不需要重复的元素. 定义RDBMS表: 考虑一个情况下,我们需要我们的员工记录存储在EMPLOYEE

基于java集合中的一些易混淆的知识点(详解)_java

(一) collection和collections 这两者均位于java.util包下,不同的是: collection是一个集合接口,有ListSet等常见的子接口,是集合框架图的第一个节点,,提供了对集合对象进行基本操作的一系列方法. 常见的方法有: boolean add(E e) 往容器中添加元素:int size() 返回collection的元素数:boolean isEmpty() 判断此容器是否为空: boolean contains(Object o) 如果此collecti

Java中成员方法与成员变量访问权限详解_java

记得在一次面试的笔试题中,有的面试官会要求写出具体的像pullic这些访问限定符的作用域.其实,平常我都没去系统的考虑这些访问限定符的作用域,特别是包内包外的情况,OK,笔试不行了.  这是java基本的知识,也是公司看重的,那没办法啦,我的脑袋记不住东西,那我只能把这些东西写下来方便自己温故知新,不废话了,贴代码了. 代码如下: package com.jaovo; /** *_1_ 成员变量访问权限的求证 * public private protected default(默认的权限) *

Java中对List集合的常用操作详解_java

目录: 1.list中添加,获取,删除元素: 2.list中是否包含某个元素: 3.list中根据索引将元素数值改变(替换): 4.list中查看(判断)元素的索引: 5.根据元素索引位置进行的判断: 6.利用list中索引位置重新生成一个新的list(截取集合): 7.对比两个list中的所有元素: 8.判断list是否为空: 9.返回Iterator集合对象: 10.将集合转换为字符串: 11.将集合转换为数组: 12.集合类型转换: 备注:内容中代码具有关联性. 1.list中添加,获取,

Java中volatile关键字的作用与用法详解_java

volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile 关键字作用是,使系统中所有线程对该关键字修饰的变量共享可见,可以禁止线程的工作内存对volatile修饰的变量进行缓存. volatile 2个使用场景: 1.可见性:Java提供了volatile关键字来保证可见性. 当一个共享变量被volatile修饰时,它会保证修

Android 中Crash时如何获取异常信息详解及实例

Android 中Crash时如何获取异常信息详解 前言: 大家都知道,Android应用不可避免的会发生crash,无论你的程序写的多完美,总是无法完全避免crash的发生,可能是由于Android系统底层的bug,也可能是由于不充分的机型适配或者是糟糕的网络状况.当crash发生时,系统会kill掉你的程序,表现就是闪退或者程序已停止运行,这对用户来说是很不友好的,也是开发者所不愿意看到的,更糟糕的是,当用户发生了crash,开发者却无法得知程序为何crash,即便你想去解决这个crash,

Android开发中原生生成JSON与解析JSON详解教程

下面分为生成JSON数据和解析JSON数据,所用的包是org.json (1)生成JSON数据方法: 比如要生成一个这样的json文本      {       "phone" : ["12345678", "87654321"],    //数组     "name" : "dream9", // 字符串        "age" : 100, // 数值       "ad