• 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

  • 相关阅读:
    从句分析
    artDialog ( v 6.0.2 ) content 参数引入页面 html 内容
    Java实现 LeetCode 13 罗马数字转整数
    Java实现 LeetCode 13 罗马数字转整数
    Java实现 LeetCode 13 罗马数字转整数
    Java实现 LeetCode 12 整数转罗马数字
    Java实现 LeetCode 12 整数转罗马数字
    Java实现 LeetCode 12 整数转罗马数字
    Java实现 LeetCode 11 盛最多水的容器
    Java实现 LeetCode 11 盛最多水的容器
  • 原文地址:https://www.cnblogs.com/guangshan/p/4886029.html
Copyright © 2020-2023  润新知