Java注解底层竟然是个Map?

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()); } } 

执行上面的代码,结果输出如下:
Java注解底层竟然是个Map?

案例二:组合注解用法


下面的代码中尝试从某个类上获取它的注解,然后再从这个注解中获取它上面的注解。组合注解在 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 ""; } 

执行上面的代码,结果输出如下:
Java注解底层竟然是个Map?
可以看到执行结果在 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注解底层竟然是个Map?

当调用 Class 类中 getAnnotation() 方法获取注解对象时,实际上是从 AnnotationDataannotations 属性中获取的,这个属性实际上就是一个 Map 对象,它的 key 是一个注解的 Class 对象,value 是 Annotation 类的一个对象实例,即注解的动态代理对象。代码如下:

public <A extends Annotation> A getAnnotation(Class<A> 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; } } 

AnnotationData 对象的创建则是在 annotationData() 方法中完成的,在这个方法里面实际上调用了 createAnnotationData() 方法来创建 AnnotationData 对象,它首先调用了 AnnotationParserparseAnnotations() 方法生成 Map 对象,然后基于它创建了 AnnotationData 对象,实现对注解的动态代理对象的生成就是在 AnnotationParser 中实现的。

private AnnotationData annotationData() { while (true) { AnnotationData annotationData = this.annotationData; int classRedefinedCount = this.classRedefinedCount; if (annotationData != null && annotationData.redefinedCount == classRedefinedCount) { return annotationData; } AnnotationData newAnnotationData = createAnnotationData(classRedefinedCount); if (Atomic.casAnnotationData(this, annotationData, newAnnotationData)) { return newAnnotationData; } } } private AnnotationData createAnnotationData(int classRedefinedCount) { // 处理当前类的注解 Map<Class<? extends Annotation>, Annotation> declaredAnnotations = AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this); Class<?> superClass = getSuperclass(); Map<Class<? extends Annotation>, Annotation> annotations = null; // 处理父类的注解 if (superClass != null) { Map<Class<? extends Annotation>, Annotation> superAnnotations = superClass.annotationData().annotations; for (Map.Entry<Class<? extends Annotation>, Annotation> e : superAnnotations.entrySet()) { Class<? extends Annotation> annotationClass = e.getKey(); if (AnnotationType.getInstance(annotationClass).isInherited()) { if (annotations == null) { // lazy construction annotations = new LinkedHashMap<>((Math.max( declaredAnnotations.size(), Math.min(12, declaredAnnotations.size() + superAnnotations.size()) ) * 4 + 2) / 3 ); } annotations.put(annotationClass, e.getValue()); } } } if (annotations == null) { annotations = declaredAnnotations; } else { annotations.putAll(declaredAnnotations); } // 创建AnnotationData对象 return new AnnotationData(annotations, declaredAnnotations, classRedefinedCount); } 

AnnotationParser 中最终会调用到它的 parseAnnotation2() 方法,在该方法中从二进制流中解析到注解的类型以及它的定义成员和值的映射关系。然后在 annotationForMap()方法中创建动态代理对象,这个动态代理的方法的调用都会调用到 AnnotationInvocationHandler 中。代码如下:

private static Annotation parseAnnotation2(ByteBuffer buf, ConstantPool constPool, Class<?> container, boolean exceptionOnMissingAnnotationClass, Class<? extends Annotation>[] selectAnnotationClasses) { int typeIndex = buf.getShort() & 0xFFFF; Class<? extends Annotation> annotationClass = null; String sig = "[unknown]"; try { try { sig = constPool.getUTF8At(typeIndex); // 这里解析到注解的实际类型 annotationClass = (Class<? extends Annotation>)parseSig(sig, container); } catch (IllegalArgumentException ex) { // support obsolete early jsr175 format class files annotationClass = (Class<? extends Annotation>)constPool.getClassAt(typeIndex); } } catch (NoClassDefFoundError e) { // 省略代码 } // 这里解析到成员和值的映射关系 Map<String, Object> memberValues = new LinkedHashMap<String, Object>(type.memberDefaults()); // 省略代码 return annotationForMap(annotationClass, memberValues); } public static Annotation annotationForMap( final Class<? extends Annotation> type, final Map<String, Object> memberValues) { return AccessController.doPrivileged(new PrivilegedAction<Annotation>() { public Annotation run() { return (Annotation) Proxy.newProxyInstance( type.getClassLoader(), new Class<?>[] { type }, new AnnotationInvocationHandler(type, memberValues)); }}); } 

AnnotationInvocationHandler 实现了 InvocationHandler 接口,而 InvocationHandler 则是实现动态代理的老朋友了。在 AnnotationInvocationHandlerinvoke() 方法中根据当前调用的方法获取名称,然后根据名称从 Map 中获取中对应的值,然后返回,这就是在代码中调用注解的方法能够拿到对应的设置的值的原理,底层的实现就是一个 Map。

class AnnotationInvocationHandler implements InvocationHandler, Serializable { private static final long serialVersionUID = 6182022883658399397L; // 这里的type就是对应的注解类型 private final Class<? extends Annotation> type; private final Map<String, Object> memberValues; public Object invoke(Object proxy, Method method, Object[] args) { // 这里获取到对应方法的名称 String member = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); // Handle Object and Annotation methods if (member.equals("equals") && paramTypes.length == 1 && paramTypes[0] == Object.class) return equalsImpl(args[0]); if (paramTypes.length != 0) throw new AssertionError("Too many parameters for an annotation method"); switch(member) { case "toString": return toStringImpl(); case "hashCode": return hashCodeImpl(); case "annotationType": return type; } // 根据方法的名称从Map中映射获取到对应的值返回 Object result = memberValues.get(member); if (result == null) throw new IncompleteAnnotationException(type, member); if (result instanceof ExceptionProxy) throw ((ExceptionProxy) result).generateException(); if (result.getClass().isArray() && Array.getLength(result) != 0) result = cloneArray(result); return result; } } 

在 Java 程序启动的时候设置参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 可以让 JDK 把给注解生成的动态代理类的 Class 文件保存到磁盘上。下面是 JDK 给 @TestComposite 注解生成的动态代理类,可以看到它内部有个 InvocationHandler 类型的实例变量,当调用 annotationType() 方法时,实际上代理到了 InvocationHandlerinvoke() 方法中。代码如下:

public final class $Proxy1 extends Proxy implements TestComposite { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public $Proxy1(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final Class annotationType() throws { try { return (Class)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("TestComposite").getMethod("annotationType"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(((Throwable)var2).getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(((Throwable)var3).getMessage()); } } } 

问题解答

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

  • 本文作者:WAP站长网
  • 本文链接: https://wapzz.net/post-27073.html
  • 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。
本站部分内容来源于网络转载,仅供学习交流使用。如涉及版权问题,请及时联系我们,我们将第一时间处理。
文章很赞!支持一下吧 还没有人为TA充电
为TA充电
还没有人为TA充电
0
  • 支付宝打赏
    支付宝扫一扫
  • 微信打赏
    微信扫一扫
感谢支持
文章很赞!支持一下吧
关于作者
2.7W+
9
1
2
WAP站长官方

文生图模型攻击论文原理笔记

上一篇

对话五条人AI MV主创:究竟是人带着AI飞,还是AI带着人类飞

下一篇
评论区
内容为空

这一切,似未曾拥有

  • 复制图片
按住ctrl可打开默认菜单