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