找回密码
 立即注册
首页 业界区 业界 Java注解底层竟然是个Map?

Java注解底层竟然是个Map?

扒钒 昨天 15:02
案例介绍

案例一:普通注解用法
下面的代码定义了一个注解 @Test,然后在 AnnotationTest 中获取到这个注解,然后打印出它 value() 方法的值。
  1. @Retention(RetentionPolicy.RUNTIME)  
  2. @Target(ElementType.TYPE)  
  3. public @interface Test {  
  4.     String value() default "";  
  5. }
  6. @Test("test")  
  7. public class AnnotationTest {  
  8.     public static void main(String[] args) {  
  9.         Test test = AnnotationTest.class.getAnnotation(Test.class);  
  10.         System.out.println(test.value());  
  11.     }  
  12. }
复制代码
执行上面的代码,结果输出如下:
1.png

案例二:组合注解用法
下面的代码中尝试从某个类上获取它的注解,然后再从这个注解中获取它上面的注解。组合注解在 Spring 中很常见,比如常用的 @RestController,它实际上就是组合了 @Controller 和 @ResponseBody 这两个注解。
  1. @Retention(RetentionPolicy.RUNTIME)  
  2. @Target(ElementType.TYPE)  
  3. public @interface Test {  
  4.     String value() default "";  
  5. }
  6. @Retention(RetentionPolicy.RUNTIME)  
  7. @Target(ElementType.TYPE)  
  8. @Test("composite")  
  9. public @interface TestComposite {  
  10. }
  11. @TestComposite
  12. public class AnnotationTest {  
  13.     public static void main(String[] args) {  
  14.         TestComposite testComposite = AnnotationTest.class.getAnnotation(TestComposite.class);  
  15.         boolean existInProxyObj = testComposite.getClass().isAnnotationPresent(Test.class);  
  16.         System.out.println("是否在动态代理对象的Class对象上存在:" + existInProxyObj);  
  17.         boolean existInOriginal = testComposite.annotationType().isAnnotationPresent(Test.class);  
  18.         System.out.println("是否在原始的Class对象上存在:" + existInOriginal);  
  19.     }  
  20. }
  21. @Target(ElementType.TYPE)
  22. @Retention(RetentionPolicy.RUNTIME)
  23. @Documented
  24. @Controller
  25. @ResponseBody
  26. public @interface RestController {
  27.     @AliasFor(annotation = Controller.class)
  28.     String value() default "";
  29. }
复制代码
执行上面的代码,结果输出如下:
2.png

可以看到执行结果在 testComposite.getClass() 返回的 Class 对象上不存在 @Test 这个注解,而是在 testComposite.annotationType() 返回的 Class 对象上才存在 @Test 这个注解。这个问题的原理是什么呢?
先说下结论:

  • 使用 @interface 定义了一个注解之后编译得到的  class 文件会默认继承 @Annotation 这个接口;
  • 在代码中获取注解时实际上是获取到的是 JDK 为它创建的一个动态代理对象。这个动态代理对象实现了注解定义的方法,当代码中调用注解的方法获取值时,实际上是调用的这个动态代理对象的方法获取到的值;
  • 接口上修饰的注解无法在其实现类上获取到;
源码分析

先来看下 Annotation 接口,它里面定义了 annotationType() 方法,也就是上面组合注解调用的方法,所有注解在编译之后生成的 class 文件都会自动继承该接口。通过查看 @Test 注解编译之后的注解对应的 class 文件,可以看到它继承了 Annotation 接口。代码和执行结果如下:
  1. public interface Annotation {
  2.     boolean equals(Object obj);
  3.    
  4.     int hashCode();
  5.    
  6.     String toString();
  7.     Class<? extends Annotation> annotationType();
  8. }
复制代码
在 Java 程序启动的时候设置参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 可以让 JDK 把给注解生成的动态代理类的 Class 文件保存到磁盘上。下面是 JDK 给 @TestComposite 注解生成的动态代理类,可以看到它内部有个 InvocationHandler 类型的实例变量,当调用 annotationType() 方法时,实际上代理到了 InvocationHandler 的 invoke() 方法中。代码如下:
  1. public  A getAnnotation(Class annotationClass) {  
  2.     Objects.requireNonNull(annotationClass);  
  3.     return (A) annotationData().annotations.get(annotationClass);  
  4. }
  5. private static class AnnotationData {  
  6.     // 这里是一个Map结构,
  7.     final Map<Class<? extends Annotation>, Annotation> annotations;  
  8.     final Map<Class<? extends Annotation>, Annotation> declaredAnnotations;  
  9.   
  10.     // Value of classRedefinedCount when we created this AnnotationData instance  
  11.     final int redefinedCount;  
  12.   
  13.     AnnotationData(Map<Class<? extends Annotation>, Annotation> annotations,  
  14.                    Map<Class<? extends Annotation>, Annotation> declaredAnnotations,  
  15.                    int redefinedCount) {  
  16.         this.annotations = annotations;  
  17.         this.declaredAnnotations = declaredAnnotations;  
  18.         this.redefinedCount = redefinedCount;  
  19.     }  
  20. }
复制代码
问题解答

基于以上分析来回答一下组合注解那里提出的问题:为什么 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 这个注解的。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册