1. 继续上一讲内容,复习上一讲内容我们讲到了 Retention以及RetentionPolicy。这两个都是成对出现的,因为Retention里面包含了一个属性value类型为 RetentionPolicy 枚举类型,它有三个枚举CLASS、RUNTIME、SOURCE。分别表示注解产生在三种不同的环境下。
2. 下面我们学习一下,如果在RUNTIME情况下,如何利用反射来读取已经Class中的注解信息。
定义Annotation时必须设定RetentionPolicy为RUNTIME,也就是可以在VM中读取Annotation信息。
1) 定义注解 MyAnnotation.java
2) 定义一个类使用上面定义的注解package com.ahuier.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /* * 使用Retention修饰这个注解, * 告诉这个注解在使用的时候即可以用到.Class文件,又能在运行过程中通过反射的方式读取出来 * 这个注解用在Mytest类 和 它里面的方法output()方法上 */ @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String hello() default "ahuier"; String world(); }
3) 定义一个类利用反射来取得注解中的信息package com.ahuier.annotation; @MyAnnotation(hello = "beijing", world = "shanghai") public class MyTest { @MyAnnotation(hello = "tianjin", world = "shangdi") @Deprecated @SuppressWarnings("unchecked") //一个方法可以有多个注解所修饰 public void output(){ System.out.println("output something!"); } }
编译执行结果:package com.ahuier.annotation; import java.lang.reflect.Method; /* * 通过反射获取类MyTest中output()方法上所有的信息,并且把信息显示出来 */ public class MyReflection { public static void main(String[] args) throws Exception{ MyTest myTest = new MyTest(); Class<MyTest> c = MyTest.class; /* * 通过反射获取method对象,由于Method实现了AnnotatedElement接口,所以它可以调用获取注解信息的API来获得注解信息。 */ Method method = c.getMethod("output", new Class[]{}); /* * 查看JDk Doc 文档,在Method类的父类AccessibleObject类中isAnnotationPresent() * 判断某一个方法上面是否存在某一个注解 */ if(method.isAnnotationPresent(MyAnnotation.class)){ method.invoke(myTest, new Object[]{}); //如果存在这个注解,则去调用MyTest类中output()方法 } } }
output something!
【说明】:以上输出方法内容,是因为我们在定义注解的时候,将注解信息 Retention 设置为“RUNTIME”
为了更好的理解注解信息三个枚举的不同,我们将注解 MyAnnotation.java 中的注解 Retention 设置为"CLASS" 和 "SOURCE" 如下代码所示:
@Retention(RetentionPolicy.CLASS) public @interface MyAnnotation { String hello() default "ahuier"; String world(); }
编译执行结果都没输出任何内容,因为设置为这两个类型的枚举值之后,它们就不会在JVM运行期保留这些注解信息,所以即便通过反射方式也是无法获得他们的信息的,也就是在执行 MyReflection.java这个类的 if(method.isAnnotationPresent(MyAnnotation.class))时候,它返回false,不执行里面的内容了。@Retention(RetentionPolicy.SOURCE) public @interface MyAnnotation { String hello() default "ahuier"; String world(); }
通过上面一个例子我们可以知道使用RUNTIME之后,是可以通过反射的方式调用到被注解修饰的方法,现在我们可以通过它们的一些API来获得注解的一些某一个方法上面特定的注解信息。
对上面第三个代码 MyReflection.java 继续写下去如下:
编译执行结果:package com.ahuier.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Method; /* * 通过反射获取类MyTest中output()方法上所有的信息,并且把信息显示出来 */ public class MyReflection { public static void main(String[] args) throws Exception{ MyTest myTest = new MyTest(); Class<MyTest> c = MyTest.class; /* * 通过反射获取method对象,由于Method实现了AnnotatedElement接口,所以它可以调用获取注解信息的API来获得注解信息。 */ Method method = c.getMethod("output", new Class[]{}); /* * 查看JDk Doc 文档,在Method类的父类AccessibleObject类中isAnnotationPresent() * 判断某一个方法上面是否存在某一个注解 */ if(method.isAnnotationPresent(MyAnnotation.class)){ method.invoke(myTest, new Object[]{}); //如果存在这个注解,则去调用MyTest类中output()方法 /* * 查看JDK Doc中Method类的public <T extends Annotation> T getAnnotation(Class<T> annotationClass)方法。 * 如果注解存在,则放回这个注解,否则返回为空 */ MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class); String hello = myAnnotation.hello(); //获取 MyAnnotation中的 hello()注解信息 String world = myAnnotation.world(); //获取 MyAnnotation中的 world()注解信息 System.out.println(hello + ", " + world); } /* * 查看JDK Doc文档中 public Annotation[] getAnnotations() * 放回所有在特定元素上的注解,如果这个元素上没有注解,则返回一个数组长度为0的数组,而非返回空的数组,所以不需要判断数组是否为空,直接遍历数组 */ Annotation[] annotations = method.getAnnotations(); for(Annotation annotation : annotations){ /* * 查看JDK文档Annotation接口的 Class<? extends Annotation> annotationType() * 返回它所对应的Class对象 * 以下输出: * com.ahuier.annotation.MyAnnotation * java.lang.Deprecated * 原因是MyTest中的output()上面的定义的三个注解中有两个注解RetentionPolicy为“RUNTIME”,如果是“CLASS” 和 "SOURCE",则得不到的 */ System.out.println(annotation.annotationType().getName()); } } }
output something!
tianjin, shangdi
com.ahuier.annotation.MyAnnotation
java.lang.Deprecated
3. 下面我们学习Target,限定annotation使用对象@Target,如下图所示:
这边的@Target与Retention有异曲同工之妙,Retention搭配RetentionPolicy来指定它的枚举值是什么。而Target也搭配ElementType表示这注解可以修饰哪些目标。
查看JDK Doc文档Target,它有一个value,是ElementType 枚举类型,查看ElementTyped,它的枚举值如下所示:
ANNOTATION_TYPE: 注解只能修饰注解,不能修饰其他的东西
CONSTRUCTOR: 注解只能修饰构造方法
FIELD: 注解只能修饰属性(成员变量)
LOCAL_VARIABLE: 注解只能修饰局部变量
METHOD: 注解只能修饰方法
PACKAGE: 注解只能修饰包
PARAMETER: 注解只能修饰方法的参数
TYPE: 注解只能修饰类、接口、枚举
如下图所示:
下面我们来通过例子学习上面的内容:
1) 定义一个注解MyTarget.java
2) 定义一个类使用这个注解,如下图所示提示错误package com.ahuier.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Target; /* * @Target(ElementType.METHOD)表示这个注解只能用来修饰方法 */ @Target(ElementType.METHOD) public @interface MyTarget { String value(); }
提示错误原因是在MyTarget这个注解中我们定义的注解只能用来修饰方法,所以在修饰类的时候会提示错误。修改的方法是将MyTarget.java注解Target的value改为TYPE则可以,如下代码所示:
@Target(ElementType.TYPE) public @interface MyTarget { String value(); }
4. 要求为API文件@Documented
定义一个带@Documented的注解
package com.ahuier.annotation; import java.lang.annotation.Documented; /* * @Documented 表示带@Documented修饰的东西会添加到生成文档的API中去 */ @Documented public @interface DocumentedAnnotation { String hello(); }
定义一个使用这个注解的类
编译生成Java DOC文档:Eclipse --> Project --> Generate Javadoc --> 选择需要生成doc的包package com.ahuier.annotation; public class DocumentedTest { @DocumentedAnnotation(hello = "welcome") public void method(){ System.out.println("hello world"); } }
生成完成打开生成API文档,查看我们定义的method()方法如下图所示,已经被标注了注解信息了。
5. 子类是否继承父类@Inherited,如下图所示:
到此为止,我们的注解已经全部讲完了,在实际开发中,我们一般很少自己去定义注解,但是通过这些内容我们可以明白注解的本源是什么,可以在以后的实际开发中更好的学习好注解。