注解本身不会自动生效,必须由处理器或反射调用;其生命周期由@Retention决定,SOURCE/CLASS/RUNTIME三类保留策略对应不同处理时机;运行时需通过反射主动读取,且Spring等功能注解依赖容器扩展点如BeanPostProcessor和AOP。

注解本身不会自动生效,必须配合处理器或反射调用
Java 注解只是元数据标记,@Override 能报错、@Test 能运行测试、@Autowired 能注入对象,这些都不是注解自己干的。真正起作用的是:编译期的 Annotation Processor(如 Lombok)、运行时的反射逻辑(如 Spring 的 AnnotationConfigApplicationContext),或字节码增强工具(如 AspectJ)。
常见误解是“加了注解就自动触发某行为”,实际必须有配套机制读取并响应它。比如自定义 @LogExecutionTime,不写 AOP 切面或代理逻辑,它永远只是个标签。
三种处理时机:SOURCE / CLASS / RUNTIME 各有用途
注解的生命周期由 @Retention 决定,选错会直接导致读不到:
-
@Retention(RetentionPolicy.SOURCE):仅保留在源码,javac编译后丢弃(如@Override)——无法通过反射获取 -
@Retention(RetentionPolicy.CLASS):保留在 class 文件但 JVM 不加载(如某些代码生成注解)——反射拿不到,需字节码库(如 ASM)解析 -
@Retention(RetentionPolicy.RUNTIME):完整保留到运行时——反射可用,Spring 大部分注解都属此类
如果写了个 @MyValid 却忘了加 @Retention(RetentionPolicy.RUNTIME),用 clazz.getAnnotation(MyValid.class) 一定返回 null,这是新手最常踩的坑。
立即学习“Java免费学习笔记(深入)”;
运行时读取注解必须用反射 API,且注意继承性
要让注解在运行时起作用,得主动调用反射方法,例如:
public class AnnotationReader {
public static void checkAnnotatedMethod(Object target) {
for (Method m : target.getClass().getDeclaredMethods()) {
if (m.isAnnotationPresent(MyRetry.class)) {
MyRetry retry = m.getAnnotation(MyRetry.class);
System.out.println("retry times: " + retry.value());
// 此处插入重试逻辑
}
}
}
}
几个关键点:
- 用
getDeclaredMethods()而非getMethods(),后者只返回 public 方法且不包含私有注解 - 若希望子类继承父类方法上的注解,需在定义注解时加
@Inherited(仅对类级别注解有效,方法/字段注解不继承) - 注解属性值必须是编译期常量(基本类型、String、Class、枚举、其他注解、以上类型的数组),不能是运行时对象
Spring 中的注解不是“魔法”,背后是 BeanPostProcessor 和 AOP
像 @Transactional 或 @Scheduled 这类功能型注解,其生效依赖 Spring 容器的扩展点:
-
@Transactional→ 由TransactionInterceptor(实现MethodInterceptor)在代理对象中织入事务逻辑 -
@Scheduled→ 在容器启动时被ScheduledAnnotationBeanPostProcessor扫描,注册到TaskScheduler -
@Value→ 由AutowiredAnnotationBeanPostProcessor在属性注入阶段解析占位符
这意味着:不在 Spring 容器管理的 bean 上使用这些注解,或者没启用对应模块(如没加 @EnableTransactionManagement),注解就完全无效。别指望 new 出来的对象能触发 @Transactional。
注解处理链路越深,越容易漏掉初始化条件或配置开关——比如忘了在配置类上加 @Configuration,整个基于注解的配置类都不会被扫描到。








