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.</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> )
</span><span style="color: rgba(0, 0, 255, 1)">public</span> @<span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> Filter {
    String value();
};
 
@Filter( </span>"filter1"<span style="color: rgba(0, 0, 0, 1)"> )
@Filter( </span>"filter2"<span style="color: rgba(0, 0, 0, 1)"> )
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> Filterable {        
}
 
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String[] args) {
    </span><span style="color: rgba(0, 0, 255, 1)">for</span>( Filter filter: Filterable.<span style="color: rgba(0, 0, 255, 1)">class</span>.getAnnotationsByType( Filter.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ) ) {
        System.out.println( filter.value() );
    }
}

}

 

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

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