一、八股原文

小林 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 这些源码层面的细节,一般一轮面试不会问到这个深度。