Java注解本质是继承Annotation的接口,需经@Retention(决定存留周期)、@Target(限定使用位置)、反射读取(RUNTIME时通过getAnnotation获取)三步驱动,注解本身不执行逻辑。

Java 注解原理要形成能应对深度追问的知识体系,关键不是死记概念,而是理清“谁定义 → 保存到哪 → 谁来读 → 读完干啥”这条完整链路。整套逻辑环环相扣,漏掉任一环节,遇到面试官问“为什么我写了注解但没生效”,就容易卡壳。
注解本质:它只是个带约束的接口
所有注解在底层都是 interface,且隐式继承 java.lang.annotation.Annotation。比如你写:
@interface MyLog { String value() default ""; }
编译后生成的是一个接口类文件(MyLog.class),不是类、不带实现、不能 new。它只声明“我能提供什么信息”,不负责“怎么用这些信息”。这点必须先立住——否则容易误以为注解本身会执行逻辑。
生命周期控制:@Retention 是行为生效的前提
注解能不能被读到,完全取决于 @Retention 策略。这是绝大多数失效问题的根源:
-
SOURCE:只在源码里存在,.java 编译成 .class 后就没了 → 只能给编译器用(如
@SuppressWarnings) - CLASS:保留在字节码中,但 JVM 加载类时不进内存 → APT 工具可用,反射拿不到
-
RUNTIME:字节码加载后仍保留在 Class 对象里 →
clazz.getAnnotation(...)才能取到 → Spring、自定义拦截器等运行时处理必须选它
没显式写 @Retention,默认是 CLASS。所以自己写的注解若想靠反射驱动,第一件事就是加上 @Retention(RetentionPolicy.RUNTIME)。
作用位置限定:@Target 决定语法合法性
@Target 不影响运行效果,但控制编译期是否允许你贴错地方:
- 标在类上却用在方法上 → 编译直接报错(如
@Service默认只允许TYPE) - 常见值:
TYPE(类/接口)、METHOD(方法)、FIELD(字段)、PARAMETER(参数) - 可组合使用:
@Target({ElementType.TYPE, ElementType.METHOD})
这层约束让 IDE 能做语法提示和错误检查,也是框架做统一扫描的基础前提。
读取与驱动:反射是运行时注解的唯一入口
注解本身不干活,真正起作用的是“读取它的代码”。对 RUNTIME 注解,核心就是反射:
- 获取类上的注解:
clazz.getAnnotation(MyLog.class) - 获取方法上的注解:
method.getAnnotation(MyLog.class) - 拿到注解实例后,调用其方法(如
log.value())取配置值 - 再基于这些值决定行为:打印日志、开启事务、抛校验异常……
Spring 的 @Transactional 就是在 Bean 初始化时,扫描所有 public 方法,找到该注解,然后用 AOP 动态织入事务逻辑——注解只是触发条件,背后全是反射 + 代理的组合拳。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











