zoukankan      html  css  js  c++  java
  • Java反射API研究(1)——注解Annotation

      注解在表面上的意思,只是标记一下这一部分,最好的注解就是代码自身。而在java上,由于注解的特殊性,可以通过反射API获取,这种特性使得注解被广泛应用于各大框架,用于配置内容,代替xml文件配置。

      要学会注解的使用,最简单的就是定义自己的注解,所以需要先了解一个java的元注解

    1、元注解--注解的注解

      元注解的作用就是负责注解其他注解,在java1.6上,只有四个元注解:@Target、@Retention、@Documented、@Inherited。在java1.8上,多了@Native与@Repeatable。下面先说说这几个元注解

      (1)、Documented

        这个纯粹是语义元注解,指示某一类型的注解将通过 javadoc 和类似的默认工具进行文档化。应使用此类型来注解这些类型的声明:其注解会影响由其客户端注解的元素的使用。如果类型声明是用 Documented 来注解的,则其注解将成为注解元素的公共 API 的一部分。被这个注解注解的注解(真拗口...)会在自动生成api文档时加载文档中。他的声明时这样的:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Documented {
    }

      (2)、Inherited

        指示注解类型被自动继承。如果在注解类型声明中存在 Inherited 元注解,并且用户在某一类声明中查询该注解类型,同时该类声明中没有此类型的注解,则将在该类的超类中自动查询该注解类型。此过程会重复进行,直到找到此类型的注解或到达了该类层次结构的顶层 (Object) 为止。如果没有超类具有该类型的注解,则查询将指示当前类没有这样的注解。 

        注意,如果使用注解类型注解类以外的任何事物,此元注解类型都是无效的。还要注意,此元注解仅促成从超类继承注解;对已实现接口的注解无效。 

        即一个类中,没有@Father的注解,但是这个类的父类有@Father注解,且@Father注解被@Inherited注解,则在使用反射获取子类@Father注解时,是可以获取到父类的@Father注解的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Inherited {
    }

      (3)、Retention

        指示注解类型的注解要保留多久。如果注解类型声明中不存在 Retention 注解,则保留策略默认为 RetentionPolicy.CLASS。只有元注解类型直接用于注解时,Target 元注解才有效。如果元注解类型用作另一种注解类型的成员,则无效。 

        某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Retention {
        RetentionPolicy value();
    }

        value值为类型为RetentionPolicy,是本包的一个枚举类型,包含三个值:

        RetentionPolicy.SOURCE  只在源代码中出现,编译器要丢弃的注解。

        RetentionPolicy.CLASS  编译器将把注解记录在类文件中,但在运行时 VM 不需要保留注解。

        RetentionPolicy.RUNTIME  编译器将把注解记录在类文件中,在运行时 VM 将保留注解,因此可以反射性地读取。

        PS:当注解中只有一个属性(或只有一个属性没有默认值),且该属性为value,则可在使用注解时直接括号中对value赋值,而不用显式指定value = RetentionPolicy.CLASS

      (4)、Target

        指示注解类型所适用的程序元素的种类。如果注解类型声明中不存在 Target 元注解,则声明的类型可以用在任一程序元素上。如果存在这样的元注解,则编译器强制实施指定的使用限制。 例如,此元注解指示该声明类型是其自身,即元注解类型。它只能用在注解类型声明上:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target {
        /**
         * Returns an array of the kinds of elements an annotation type
         * can be applied to.
         * @return an array of the kinds of elements an annotation type
         * can be applied to
         */
        ElementType[] value();
    }

        要想声明只能用于某个注解的成员类型使用的注解,则:

        @Target({}) 

        ElementType 常量在 Target 注解中至多只能出现一次,如下是非法的:

        @Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})

        value数组类型为ElementType,同样是本包的一个枚举类型,他包含的值较多,参考如下:ElementType.

    ANNOTATION_TYPE 注解类型声明
    CONSTRUCTOR 构造方法声明
    FIELD 字段声明(包括枚举常量)
    LOCAL_VARIABLE 局部变量声明
    METHOD 方法声明
    PACKAGE 包声明
    PARAMETER 参数声明
    TYPE 类、接口(包括注解类型)或枚举声明

      (5)、Repeatable  可重复注解的注解

        允许在同一申明类型(类,属性,或方法)的多次使用同一个注解。

        在这个注解出现前,一个位置要想注两个相同的注解,是不可能的,编译会出错误。所以要想使一个注解可以被注入两次,需要声明一个高级注解,这个注解中的成员类型为需要多次注入的注解的注解数组,如:

    public @interface Authority {
         String role();
    }
     
    public @interface Authorities {
        Authority[] value();
    }
     
    public class RepeatAnnotationUseOldVersion {
        @Authorities({@Authority(role="Admin"),@Authority(role="Manager")})
        public void doSomeThing(){
        }
    }

        由另一个注解来存储重复注解,在使用时候,用存储注解Authorities来扩展重复注解。这样可以实现为一个方法注解两个Authority,但是这样可读性比较差。

        通过Repeatable可以这样实现上面的效果:

    @Repeatable(Authorities.class)
    public @interface Authority {
         String role();
    }
     
    public @interface Authorities {
        Authority[] value();
    }
     
    public class RepeatAnnotationUseNewVersion {
        @Authority(role="Admin")
        @Authority(role="Manager")
        public void doSomeThing(){ }
    }

      在注解Authority上告诉该注解,如果多次用Authority注解了某个方法,则自动把多次注解Authority作为Authorities注解的成员数组的一个值,当取注解时,可以直接取Authorities,即可取到两个Authority注解。要求:@Repeatable注解的值的注解类Authorities.class,成员变量一定是被注解的注解Authority的数组。

      不同的地方是,创建重复注解Authority时,加上@Repeatable,指向存储注解Authorities,在使用时候,直接可以重复使用Authority注解。从上面例子看出,java 8里面做法更适合常规的思维,可读性强一点

      其实和第一种是一模一样的,只是增加了可读性。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Repeatable {
        /**
         * Indicates the <em>containing annotation type</em> for the
         * repeatable annotation type.
         * @return the containing annotation type
         */
        Class<? extends Annotation> value();
    }

      (6)、Native  

        Indicates that a field defining a constant value may be referenced from native code. The annotation may be used as a hint by tools that generate native header files to determine whether a header file is required, and if so, what declarations it should contain.

        仅仅用来标记native的属性

    @Documented
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Native {
    }

         只对属性有效,且只在代码中使用,一般用于给IDE工具做提示用。

     2、编写自己的注解:注解接口  Annotation

      所有注解默认都实现了这个接口,实现是由编译器完成的,编写自己的接口的方法:

      使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。同时value属性是一个注解的默认属性,只有value属性时是可以不显示赋值的。

      定义注解格式:

      public @interface 注解名 {定义体}

      使用注解格式:

      @注解名(key=value, key=value)

      注解参数的可支持数据类型: 

      1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
      2.String类型
      3.Class类型
      4.enum类型
      5.Annotation类型
      6.以上所有类型的数组

      Annotation类型里面的参数该怎么设定: 
      第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;   
      第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,Annotation等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;数组类型类似于String[] value();
      第三,如果只有一个参数成员,最好把参数名称设为"value",后加小括号.或者只有一个参数没有默认值,其他都有,也可以把这个参数名称设为"value",这样使用注解时就不用显式声明属性了。

      第四,如果一个参数成员类型为数组,如果 String[] array();传值方式为array={"a","b"},若只有一个值,则可以直接令array="a",会自动生成一个只包含a的数组。若没有值,则array={}。都是可以的。 

      注解元素的默认值:
        注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为每个注解的声明中,所有元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,一次表示某个元素不存在,在定义注解时,这已经成为一个习惯用法。

      Annotation接口中方法:

      Class<? extends Annotation> annotationType()   返回此 annotation 的注解类型。

      boolean equals(Object obj)    如果指定的对象表示在逻辑上等效于此接口的注解,则返回 true。

      String toString()  返回此 annotation 的字符串表示形式。

      所有Annotation类中的Class<?> getClass()。

    3、通过反射获取Annotation类对象

      注解对象是在一个类的class对象中的,一个类只有一个class实例,所以Annotation也是唯一的,对应于一个class文件。注:一个Class对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如,int不是类,但int.class是一个Class类型的对象。虚拟机为每个类型管理一个Class对象。因此,可以用==运算符实现两个类对象比较的操作。

      如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器。  

      反射中获取注解的类库,注解处理器类库(java.lang.reflect.AnnotatedElement):

      Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:

    说明 对应的ElementType
    Class 类定义 TYPE、ANNOTATION_TYPE
    Constructor 构造器定义 CONSTRUCTOR
    Field 类的成员变量定义 FIELD
    Method 类的方法定义 METHOD
    Package 类的包定义 PACKAGE

      注1:TYPE其实已经包含了ANNOTATION_TYPE,这个只是为了更细分

      注2:上面没有提到的ElementType.PARAMETER,可以使用Method类的Annotation[][] getParameterAnnotations() 方法获取,多个参数每个参数都可能有多个注解,所以才是二维数组。

      注3:LOCAL_VARIABLE暂时不知道怎么获取,好像也没啥必要获取。

       方法使用:AnnotatedElement接口中有四个方法,用于获取注解类型

      <T extends Annotation> T getAnnotation(Class<T> annotationClass)   如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。

      Annotation[] getAnnotations()   返回此元素上存在的所有注释。

      Annotation[] getDeclaredAnnotations()   返回直接存在于此元素上的所有注释。

      boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)  如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。

      用法:注解类型 anno = Class.getAnnotation(注解类型.class)

        之后就可以调用注解类型中的属性来获取属性值了。示例:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Father {
        String value();
    }
    
    @Father("bca")
    public class Son {
        public static void main(String[] args) {
            Father father = Son.class.getAnnotation(Father.class);
            System.out.println(father.value());
        }
    }

       Java8中又补充了三个方法,用于对@Repeatable进行支持:

      default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)  返回直接存在于此元素上的指定类型的注释。忽略继承的注解。

      default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)  返回重复注解的类型,被同注解注解的元素返回该类型注解的数组。

      default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> anotationClass)  返回重复注解的类型,被同注解注解的元素返回注解的数组。忽略继承的注解。

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Repeatable;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
     
    public class RepeatingAnnotations {
        @Target( ElementType.TYPE )
        @Retention( RetentionPolicy.RUNTIME )
        public @interface Filters {
            Filter[] value();
        }
         
        @Target( ElementType.TYPE )
        @Retention( RetentionPolicy.RUNTIME )
        @Repeatable( Filters.class )
        public @interface Filter {
            String value();
        };
         
        @Filter( "filter1" )
        @Filter( "filter2" )
        public interface Filterable {        
        }
         
        public static void main(String[] args) {
            for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
                System.out.println( filter.value() );
            }
        }
    }

      注意:Annotation是一个特殊的class,类似于enum,由于与普通class的特异性,使用getAnnocation获取的返回值,其实Annotation的代理类:sun.reflect.annotation.AnnotationInvocationHandle,所有对注解内属性的访问都是通过代理类实现的。关于代理请看后面文章。

      http://www.2cto.com/kf/201502/376988.html

  • 相关阅读:
    [独库骑行之行路难]行路难!
    [独库骑行之我们穿过草原]巴音布鲁克大草原
    [独库骑行之我们路过湖泊]天山的高山湖泊
    [Tips]通过retintolibc方法编写通用exp的一个小技巧
    [独库骑行之奇山异石]丹霞地貌和雅丹地貌
    [独库骑行之我们穿过草原]美丽的乔尔玛草原
    [独库骑行之我们路过沙漠]塔克拉玛干的边缘
    [独库骑行之我们路过森林]那拉提的山林
    大家新年快乐!
    记忆力衰退
  • 原文地址:https://www.cnblogs.com/guangshan/p/4886029.html
Copyright © 2011-2022 走看看