案例介绍

案例一:普通注解用法

下面的代码定义了一个注解 @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();
}

当调用 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 这个注解的。

Java注解底层竟然是个Map?的更多相关文章

  1. Java注解与自己定义注解处理器

    动机 近期在看ButterKnife源代码的时候.竟然发现有一个类叫做AbstractProcessor,并且ButterKnife的View绑定不是依靠反射来实现的,而是使用了编译时的注解,自己主动 ...

  2. 干掉前端!3分钟纯 Java 注解搭个管理系统

    大家好,我是小富~ 最近接触到个新项目,发现它用了一个比较有意思的框架,可以说实现了我刚入行时候的梦想,所以这里马不停蹄的和大家分享下. 在我刚开始工作接触的项目都还没做前后端分离,经常需要后端来维护 ...

  3. 小白都能学会的Java注解与反射机制

    前言 Java注解和反射是很基础的Java知识了,为何还要讲它呢?因为我在面试应聘者的过程中,发现不少面试者很少使用过注解和反射,甚至有人只能说出@Override这一个注解.我建议大家还是尽量能在开 ...

  4. java注解(Annotation)解析

    注解(Annotation)在java中应用非常广泛.它既能帮助我们在编码中减少错误,(比如最常见的Override注解),还可以帮助我们减少各种xml文件的配置,比如定义AOP切面用@AspectJ ...

  5. JAVA 注解的几大作用及使用方法详解

    JAVA 注解的几大作用及使用方法详解 (2013-01-22 15:13:04) 转载▼ 标签: java 注解 杂谈 分类: Java java 注解,从名字上看是注释,解释.但功能却不仅仅是注释 ...

  6. JAVA 注解的几大作用及使用方法详解【转】

    java 注解,从名字上看是注释,解释.但功能却不仅仅是注释那么简单.注解(Annotation) 为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后 某个时刻方便地使用这些数据(通过 解 ...

  7. Java注解(二):实战 - 直接使用对象列表生成报表

    通过对Java注解(一):介绍,思想及优点学习了解,相信大家对Java注解有一定程度的了解,本篇文章将实战项目中的应用来加深对Java注解的了解. 本实例实现根据指定字段的JavaBean,生成对应列 ...

  8. Java注解(一):介绍,作用,思想及优点

    “注解优先于命令模式”-出自<Effective Java> Java 注解,从名字上看是注释,解释.但功能却不仅仅是注释那么简单.注解(Annotation) 为我们在代码中添加信息提供 ...

  9. Java集合中List,Set以及Map等集合体系详解

    转载请注明出处:Java集合中List,Set以及Map等集合体系详解(史上最全) 概述: List , Set, Map都是接口,前两个继承至collection接口,Map为独立接口 Set下有H ...

  10. 理解Java注解类型

    一. 理解Java注解 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类.而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy ...

随机推荐

  1. 2025年Android面试题含答案

    今年过完年,毫无悬念,成了失业人员之一,于是各种准备面试.前后将近一个月时间,面试10几家公司,基本上80%的企业都拿到了offer.这里面基本上大部分都是小企业居多,少部分中厂,两三家大厂.我并没有 ...

  2. MarchingCube算法之C#实现三维❤

    首先致谢该博文,讲解的非常详细:https://blog.csdn.net/u013339596/article/details/19167907?spm=1001.2101.3001.6650.7& ...

  3. Hitachi Vantara Programming Contest 2024(AtCoder Beginner Contest 368)题解A~D

    A - Cut 题意: 将数组的后k个字符移到前面 思路: 可以用rotate()函数让数组中的元素滚动旋转 rotate(v.begin(), v.begin() + n - k, v.end()) ...

  4. Win32汇编学习笔记09.SEH和反调试

    Win32汇编学习笔记09.SEH和反调试-C/C++基础-断点社区-专业的老牌游戏安全技术交流社区 - BpSend.net SEH - structed exception handler 结构化 ...

  5. K8s新手系列之DaemonSet资源

    概述 官网文档:https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/daemonset/ DaemonSet简称ds Dae ...

  6. Mysql高级操作(select嵌套,多表JOIN)

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  7. 假如给你1亿的Redis key,如何高效统计?

    前言 有些小伙伴在工作中,可能遇到过这样的场景:老板突然要求统计Redis中所有key的数量,你随手执行了KEYS *命令,下一秒监控告警疯狂闪烁--整个Redis集群彻底卡死,线上服务大面积瘫痪. ...

  8. ArkUI-X与Android桥接通信之方法回调

    平台桥接用于客户端(ArkUI)和平台(Android或iOS)之间传递消息,即用于ArkUI与平台双向数据传递.ArkUI侧调用平台的方法.平台调用ArkUI侧的方法.本文主要介绍Android平台 ...

  9. Python之multiprocessing.Pool(创建多个子进程)

    当需要创建的子进程数量不多的时候可以直接利用multiprocessing中的Process动态生成多个进程.但是,如果是成百上千个任务,手动创建显然不合适,此时就可以用multiprocessing ...

  10. [GESP样题 七级] 最长不下降子序列题解

    题目传送门 题目大意 给定一个有向图无环图\(G\),在这个图中寻找一条路径,是这条路径上的点权所组成的序列的最长不下降子序列的长度最长. 思路部分 解决无后效性 求一个有向无环图中的最长不下降子序列 ...