java注解在运行时由jvm通过动态代理机制实现,实际返回的是proxy代理对象而非真实实例,其背后由annotationinvocationhandler驱动,从字节码常量池读取属性值并支持默认值填充与只读访问。

因为 Java 运行时对注解的处理,本质上是用动态代理机制“模拟”出一个注解接口的实现——你拿到的不是真实类的实例,而是 JVM 在堆里临时生成的 $Proxy1 这样的代理对象,它背后由 AnnotationInvocationHandler 驱动,从常量池里读取属性值。
注解本身是个接口,不能直接 new
你写的 @interface MyAnno,编译后就是个继承 java.lang.annotation.Annotation 的空接口。接口不能实例化,JVM 不会为它生成构造器,也不会在 classpath 下放一个实现类。所以反射调用 getAnnotation() 时,必须“造一个出来”——而最轻量、最可控的方式,就是用动态代理。
- 这个代理类实现了你的注解接口(比如
MyAnno.class) - 它的所有方法调用,最终都转发给同一个
AnnotationInvocationHandler实例 - handler 内部持有一个
Map<string object> memberValues</string>,键是注解属性名,值来自 class 文件的常量池
反射获取注解时,Proxy.newProxyInstance() 就悄悄执行了
当你调用 field.getAnnotation(MyAnno.class),底层流程是:
FastAPI + Flask 混合部署最佳实践,解决路由定义、API 代理等常见问题,适用于同时运行 FastAPI API 与 Flask 前端的场景。
- JVM 检查该注解是否在运行时保留(
@Retention(RUNTIME)) - 解析字节码,从
RuntimeVisibleAnnotations属性中提取原始键值对(如{"value": "hello"}) - 调用
Proxy.newProxyInstance(),传入:类加载器、注解接口数组、预设好的AnnotationInvocationHandler实例 - 返回的代理对象,类型是
MyAnno,但getClass().getName()显示的是类似$Proxy123
你能感知到它是代理,但通常不需要关心
这种设计对使用者透明:你调 anno.value(),就像调普通接口方法一样;它不会抛 NPE,也不需要手动处理 null——因为 handler 已经把默认值(如 String value() default "")填进 memberValues 了。
- 你可以用
Proxy.isProxyClass(anno.getClass())验证它确实是 JDK 动态代理生成的 - 但不能用
anno instanceof Annotation来判断(它确实实现了Annotation,但这是代理实现的) - 更不能试图序列化这个代理对象——
AnnotationInvocationHandler不是Serializable,且内部含不可序列化的元数据引用
这不是 bug,是刻意为之的设计权衡
不用真实类,是因为注解信息在编译期就固化在 class 文件里,运行时只需“只读视图”;用代理,既满足接口契约,又避免为每个注解定义一堆无意义的实现类,还天然支持属性默认值、延迟解析、安全封装等特性。
所以你看到的注解对象,表面是接口实例,背后是 JVM 精心包装的一层轻量代理——它不占内存、不需维护生命周期,只在你伸手要值的时候,才从常量池里轻轻一取。










