一、八股原文
小林 Coding 关于注解原理的原文是:
注解本质是一个继承了 Annotation 的特殊接口,其具体实现类是 Java 运行时生成的动态代理类。我们通过反射获取注解时,返回的是 Java 运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用 AnnotationInvocationHandler 的 invoke 方法。该方法会从 memberValues 这个 Map 中索引出对应的值。而 memberValues 的来源是 Java 常量池。
下面拆成 4 层来看。
二、第一层:注解是「特殊接口」
平时写的普通接口:
public interface UserService {
void hello();
}注解长得很像,但它是一种独立的类型:
public @interface MyAnnotation {
String value();
}@interface 和 interface 不是一回事:
@interface → 声明一个「注解类型」
interface → 声明一个普通接口定义好之后就能往类上贴:
@MyAnnotation("hello")
public class User {
}三、第二层:它隐式继承了 Annotation
所有注解类型都隐式继承 java.lang.annotation.Annotation。你不用自己写 extends Annotation,Java 规定好了:
MyAnnotation
↓
java.lang.annotation.Annotation所以这行会返回 true:
Annotation.class.isAssignableFrom(MyAnnotation.class); // true但别把它理解成普通接口那种简单的
implements关系。说「所有注解类型都属于 Annotation 体系」是对的,再往下就不是普通接口那一套了。
四、第三层:实现类是运行时生成的动态代理类
假设:
public @interface MyAnnotation {
String value();
}
@MyAnnotation("hello")
public class User {
}然后去取这个注解(运用到了反射的知识!):
MyAnnotation annotation = User.class.getAnnotation(MyAnnotation.class);你可能以为 annotation 是一个 MyAnnotation 对象,其实它不是你 new 出来的,而是 Java 运行时给你的一个代理对象。粗略可以想成:
// 帮助理解的伪代码,JDK 真正生成的代理类不是这么简单写出来的
class MyAnnotationProxy implements MyAnnotation {
@Override
public String value() {
return "hello";
}
}代理对象:它和目标实现同一个接口,对外看起来和目标一模一样。有点像秘书和老板的关系,办的业务是一模一样的,但是秘书帮老板干了所有事情,但是秘书(代理对象)并不是老板(真实对象)
这里补一个原文没提、但很关键的点:
MyAnnotation必须标@Retention(RetentionPolicy.RUNTIME),否则getAnnotation()拿到的直接是null。
五、为什么需要代理对象
因为 MyAnnotation 本质上是一个接口,而接口不能:
new MyAnnotation(); // 编译报错但你确实需要一个能用的对象。于是 JDK 在运行时生成一个实现了该注解接口的代理对象交给你。
所以:
annotation.value();实际上是在调用这个代理对象的方法。
但是实际上,这个 annotation 不是普通对象,是 JDK 动态代理生成的代理对象。
只要调用代理对象上任何接口方法,**不会直接执行方法体(注解接口本来就没有方法体),全部被拦截,统一进入 AnnotationInvocationHandler.invoke() 。
这个动作,就叫代理对象拦截调用。
拦截调用:JDK 发现这是代理对象的接口方法,不尝试执行不存在的方法体,直接把这次方法调用(方法名 value、参数)交给 AnnotationInvocationHandler 的 invoke()
六、AnnotationInvocationHandler 是什么
这是原文最容易把人绕晕的地方。JDK 内部的链路大概是这样:
annotation.value()
↓
代理对象拦截调用
↓
InvocationHandler
↓
AnnotationInvocationHandler
↓
invoke()
↓
按方法名找到对应的值
↓
返回 "hello"所以不用把它想得多神秘。它本质上就是帮这个注解代理对象处理方法调用的一个处理器。
七、先看一个普通的动态代理例子
不用注解,先看最普通的动态代理长什么样。
public interface Person {
String getName();
}常规做法是自己写实现类:
Person person = new PersonImpl();
person.getName();person
↓
PersonImpl
↓
getName()动态代理把它换成:
person
↓
代理对象(运行时生成,没有源码)
↓
InvocationHandler
↓
invoke()
↓
决定返回什么// 1. 定义调用处理器:所有接口方法调用都会进到这里
InvocationHandler handler = (proxy, method, args) -> {
System.out.println("有人调用了:" + method.getName());
return "张三";
};
// 2. 创建代理对象
Person person = (Person) Proxy.newProxyInstance(
Person.class.getClassLoader(),
new Class[]{Person.class},
handler
);
System.out.println(person.getName());输出:
有人调用了:getName
张三我一行 class PersonImpl implements Person 都没写,person.getName() 照样能调。这就是动态代理。

八、注解走的是同一套机制
回到:
@MyAnnotation("hello")
public class User {
}MyAnnotation annotation = User.class.getAnnotation(MyAnnotation.class);拿到的就是运行时生成的代理对象。所以 annotation.value() 大概走:
annotation.value()
↓
动态代理对象
↓
InvocationHandler
↓
AnnotationInvocationHandler.invoke()
↓
找到 "value"
↓
返回 "hello"和上一节的 Person 是同一个套路,只不过这次接口是注解类型。

九、memberValues 是什么
这部分其实很简单。假设注解有多个成员:
public @interface MyAnnotation {
String value();
int age();
}@MyAnnotation(value = "张三", age = 20)
public class User {
}运行时,代理对象内部得记住这两个键值对:
value → "张三"
age → 20这种结构正好用 Map 装:
Map<String, Object> memberValues = new HashMap<>();
memberValues.put("value", "张三");
memberValues.put("age", 20);于是:
annotation.value();可以理解成 memberValues.get("value"),得到 "张三";annotation.age() 就是 memberValues.get("age"),得到 20。
这就是原文说的:AnnotationInvocationHandler 的 invoke 方法,会从 memberValues 这个 Map 里索引出对应的值。
十二、回忆用的那张图
代码
│ @MyAnnotation("hello")
↓
编译
↓
User.class
│ 保存注解相关元数据
↓
程序运行
↓
getAnnotation()
↓
JDK 解析注解信息
↓
生成 / 返回注解代理对象
↓
MyAnnotation annotation
↓
annotation.value()
↓
代理对象拦截调用
↓
AnnotationInvocationHandler
↓
invoke()
↓
memberValues
│ value → "hello"
↓
返回 "hello"简单的来说,注解原理就是 :
注解就是写在代码上的元数据;运行时注解可以被反射读取;JDK 用代理对象表示这个注解,调用注解方法时,代理机制会从保存的注解信息里把对应的值取出来。
Question:结合文档内容,简单说说注解的底层实现
可以按「编译期存下来、运行期造对象、调用时查 Map」三步说:
第一步,注解类型编译后是一个继承 Annotation 的接口。 用 @interface 声明的注解本质是特殊的接口类型,隐式继承 java.lang.annotation.Annotation,所以 Annotation.class.isAssignableFrom(MyAnnotation.class) 返回 true。
第二步,注解值在编译后进入 class 文件的注解元数据。 写的 @MyAnnotation("hello") 会被编译进 User.class,以注解属性的形式保存。运行时通过反射 User.class.getAnnotation(MyAnnotation.class) 读取时,JDK 解析这些元数据。
第三步,因为注解是接口、不能直接 new,JDK 就用动态代理生成一个实现了该注解接口的代理对象返回。 调用 annotation.value() 时,调用先被代理拦截,交给 sun.reflect.annotation.AnnotationInvocationHandler 的 invoke() 方法处理。JDK 事先把注解的成员名和值放进 handler 内部的一个 Map<String, Object> memberValues,invoke() 直接从里面按方法名取值返回,所以 annotation.value() 等价于 memberValues.get("value")。
另外,注解必须标 @Retention(RetentionPolicy.RUNTIME) 才能在运行时被反射读到;不加时默认是 CLASS 级别,编译后就丢了,getAnnotation() 返回 null。
如果面试官继续追问 JDK 内部怎么生成代理类、memberValues 具体怎么解析,那属于 ProxyGenerator、AnnotationParser 这些源码层面的细节,一般一轮面试不会问到这个深度。