java注解的retention策略决定其生命周期:source仅存于源码,零运行时开销;class保留在字节码但不加载到jvm,供构建工具使用;runtime保留至运行时,支持反射但带来性能损耗,应按需谨慎选用。

Java注解本身不执行逻辑,但它的保留策略直接影响运行时开销、内存占用和构建流程。选错Retention,轻则多加载无用元数据,重则拖慢启动速度、暴露调试信息、干扰字节码工具。
RUNTIME策略:用得最多,也最容易滥用
这类注解会完整保留在.class文件里,并在类加载时进入JVM方法区,反射调用时可即时获取。Spring的@Component、@Autowired,Jackson的@JsonInclude,都依赖它。
但代价明确:
- 每个带RUNTIME注解的类/方法/字段,都会增加常量池条目和RuntimeVisibleAnnotations属性
- 类加载阶段需解析并缓存注解结构,增大元空间压力
- 反射读取(如getAnnotation())比普通字段访问慢10–100倍,高频调用需缓存结果
建议只对真正需要动态行为控制的场景启用RUNTIME:
- 框架扫描(如Spring Component扫描)
- 运行时条件判断(如@LogExecution是否开启)
- 序列化/反序列化策略(如@JsonFormat(pattern = "yyyy-MM-dd"))
- 不要为仅用于文档生成或IDE提示的注解设为RUNTIME
CLASS策略:静默存在,专供构建期工具
注解写入字节码,但JVM运行时不加载、不暴露给反射API。Lombok的@Getter、@Builder,以及部分APT(Annotation Processing Tool)生成器都用它。
优势在于:
- 构建工具(如Gradle插件、字节码增强器)可在不启动JVM的情况下分析.class文件
- 避免运行时内存膨胀,也不影响类加载性能
- 适合做编译后校验、安全扫描、覆盖率注入等
典型适用场景:
- 字节码插桩(如监控埋点、事务代理)
- 构建时代码验证(如检查@NonNull字段是否被初始化)
- APT生成辅助类(如Room数据库的DAO实现类)
注意:CLASS注解无法通过Class.getAnnotations()获取,必须用ASM、Byte Buddy等字节码库读取
SOURCE策略:零运行时成本,纯编译期服务
注解只存在于.java源文件中,javac一过就丢弃,不会出现在.class里。@Override、@SuppressWarnings、Lombok的@UtilityClass默认都是SOURCE。
这是性能最优的选择,适用于:
- 编译器静态检查(类型安全、空值警告、线程安全标注)
- IDE智能提示与重构支持(如@NotNull触发自动判空)
- 自定义注解处理器(javac -processor)生成新源码(如Protocol Buffer的@Proto)
关键提醒: - 不要试图在运行时反射读取SOURCE注解——必定返回null
- 若你写的注解只用于生成getter/setter或日志模板,就该用SOURCE而非RUNTIME
配置原则:够用就好,宁窄勿宽
- 默认从SOURCE起步,只有确认需要运行时能力再升级
- 同一注解不要同时声明多个Retention(Java不允许多个@Retention)
- 对框架使用者:优先查阅文档确认注解策略,避免误配导致功能失效(如把@Service设为SOURCE,Spring就扫不到)
- 对框架开发者:提供策略配置选项(如@EnableAsync(mode = AdviceMode.PROXY vs ASPECTJ),让使用者按需选择
不复杂但容易忽略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











