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

posted @ 2015-10-16 18:12  光闪  阅读(3314)  评论(0编辑  收藏  举报