窝酴 发表于 4 天前

Java注解底层竟然是个Map?

案例介绍

案例一:普通注解用法
下面的代码定义了一个注解 @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() 方法中。代码如下:
publicA 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 这个注解的。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: Java注解底层竟然是个Map?