最直接的防范方式是把@retention(retentionpolicy.runtime)当作自定义注解的“出厂标配”,因aop依赖反射读取注解,若缺失则默认retentionpolicy.class导致getannotation()返回null,切点匹配失败、通知静默失效;需通过模板化创建、ci静态检查和启动校验三道防线防控。

最直接的防范方式是把 @Retention(RetentionPolicy.RUNTIME) 当作自定义注解的“出厂标配”,只要这个注解需要被 AOP、校验框架或任何运行时反射读取,就必须显式声明。
为什么删掉 @Retention 就会瘫痪
Spring AOP 的切面逻辑依赖反射获取注解信息。如果自定义注解没写 @Retention,Java 默认使用 RetentionPolicy.CLASS(只保留在字节码中,不加载进 JVM 运行时),getAnnotation() 调用永远返回 null——切点匹配失败,整个环绕通知、前置通知等全部跳过,相当于切面“消失”了。
这种问题不会在编译时报错,也不会在启动时报异常,而是在请求进来后静默失效,极难定位。
三道防线防止误删
-
模板化创建注解:在 IDE 中配置 Live Template(如 IntelliJ 的
annrt),输入即自动补全完整结构:@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented -
CI 阶段静态检查:用 SpotBugs 或自定义 Checkstyle 规则扫描所有
@interface定义,强制要求存在@Retention(RetentionPolicy.RUNTIME);缺失则构建失败 -
启动时主动校验:在 Spring Boot
@PostConstruct或ApplicationRunner中遍历所有自定义注解类,调用annotation.getAnnotation(Retention.class),若为null或值不是RUNTIME,直接抛出IllegalStateException中断启动
如何快速发现已误删的情况
上线前加一个轻量级 smoke test:
- 写一个带该注解的测试方法(如
@MyRetryable) - 在测试中通过
clazz.getDeclaredMethod("xxx").getAnnotation(MyRetryable.class)断言结果非空 - 集成进单元测试流水线,每次 PR 都跑一遍
比等线上报 500 再翻日志查原因快得多。










