案例介绍
案例一:普通注解用法
下面的代码定义了一个注解 @Test,然后在 AnnotationTest 中获取到这个注解,然后打印出它 value() 方法的值。- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.TYPE)
- public @interface Test {
- String value() default "";
- }
- @Test("test")
- public class AnnotationTest {
- public static void main(String[] args) {
- Test test = AnnotationTest.class.getAnnotation(Test.class);
- System.out.println(test.value());
- }
- }
复制代码 执行上面的代码,结果输出如下:
案例二:组合注解用法
下面的代码中尝试从某个类上获取它的注解,然后再从这个注解中获取它上面的注解。组合注解在 Spring 中很常见,比如常用的 @RestController,它实际上就是组合了 @Controller 和 @ResponseBody 这两个注解。- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.TYPE)
- public @interface Test {
- String value() default "";
- }
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.TYPE)
- @Test("composite")
- public @interface TestComposite {
- }
- @TestComposite
- public class AnnotationTest {
- public static void main(String[] args) {
- TestComposite testComposite = AnnotationTest.class.getAnnotation(TestComposite.class);
- boolean existInProxyObj = testComposite.getClass().isAnnotationPresent(Test.class);
- System.out.println("是否在动态代理对象的Class对象上存在:" + existInProxyObj);
- boolean existInOriginal = testComposite.annotationType().isAnnotationPresent(Test.class);
- System.out.println("是否在原始的Class对象上存在:" + existInOriginal);
- }
- }
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Controller
- @ResponseBody
- public @interface RestController {
- @AliasFor(annotation = Controller.class)
- String value() default "";
- }
复制代码 执行上面的代码,结果输出如下:
可以看到执行结果在 testComposite.getClass() 返回的 Class 对象上不存在 @Test 这个注解,而是在 testComposite.annotationType() 返回的 Class 对象上才存在 @Test 这个注解。这个问题的原理是什么呢?
先说下结论:
- 使用 @interface 定义了一个注解之后编译得到的 class 文件会默认继承 @Annotation 这个接口;
- 在代码中获取注解时实际上是获取到的是 JDK 为它创建的一个动态代理对象。这个动态代理对象实现了注解定义的方法,当代码中调用注解的方法获取值时,实际上是调用的这个动态代理对象的方法获取到的值;
- 接口上修饰的注解无法在其实现类上获取到;
源码分析
先来看下 Annotation 接口,它里面定义了 annotationType() 方法,也就是上面组合注解调用的方法,所有注解在编译之后生成的 class 文件都会自动继承该接口。通过查看 @Test 注解编译之后的注解对应的 class 文件,可以看到它继承了 Annotation 接口。代码和执行结果如下:- public interface Annotation {
- boolean equals(Object obj);
-
- int hashCode();
-
- String toString();
- Class<? extends Annotation> annotationType();
- }
复制代码 在 Java 程序启动的时候设置参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 可以让 JDK 把给注解生成的动态代理类的 Class 文件保存到磁盘上。下面是 JDK 给 @TestComposite 注解生成的动态代理类,可以看到它内部有个 InvocationHandler 类型的实例变量,当调用 annotationType() 方法时,实际上代理到了 InvocationHandler 的 invoke() 方法中。代码如下:- public A getAnnotation(Class annotationClass) {
- Objects.requireNonNull(annotationClass);
- return (A) annotationData().annotations.get(annotationClass);
- }
- private static class AnnotationData {
- // 这里是一个Map结构,
- final Map<Class<? extends Annotation>, Annotation> annotations;
- final Map<Class<? extends Annotation>, Annotation> declaredAnnotations;
-
- // Value of classRedefinedCount when we created this AnnotationData instance
- final int redefinedCount;
-
- AnnotationData(Map<Class<? extends Annotation>, Annotation> annotations,
- Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
- int redefinedCount) {
- this.annotations = annotations;
- this.declaredAnnotations = declaredAnnotations;
- this.redefinedCount = redefinedCount;
- }
- }
复制代码 问题解答
基于以上分析来回答一下组合注解那里提出的问题:为什么 testComposite.getClass() 返回的 Class 对象上不存在 @Test 这个注解,而是在 testComposite.annotationType() 返回的 Class 对象上才存在 @Test 这个注解呢?
testComposite.getClass() 方法返回的 Class 对象实际上就是上面的 $Proxy1.class,它实现了 TestComposite 接口,而 @Test 注解实际上是标记在 @TestComposite 注解上的(本质上是一个接口),根据 Java 的规则,接口上面的注解在其实现类上是获取不到的,因此testComposite.getClass() 返回的 Class 对象上不存在 @Test 这个注解;
testComposite.annotationType() 返回的 Class 对象实际上就是 TestComposite.class 这个 Class 对象,而 @Test 注解实际上是标记在 @TestComposite 注解上的(本质上是一个接口),所以它上面是存在 @Test 这个注解的。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |