符合jsr标准的自定义注解关键在于元注解组合的行为合规:需根据读取主体(编译器/apt/运行时框架)、时机(source/class/runtime)和用途(校验/生成代码/反射)精准选择@retention、@target等元注解,并满足jsr-303等规范的扩展要求。

设计符合JSR标准的自定义注解,关键不在“写得像”,而在“行为合规”——即元注解组合必须严格匹配目标使用场景的生命周期、作用域和语义契约。比如,若注解需被框架在运行时通过反射读取(如Spring AOP或MyBatis拦截),@Retention(RetentionPolicy.RUNTIME)就是硬性前提;若仅用于编译检查(如自定义@NonNull),则应选SOURCE。
明确注解用途,反推元注解组合
先问自己三个问题:这个注解谁来读?什么时候读?读完干什么?答案直接决定元注解选型:
- 由编译器读取并报错/警告 → 用 @Retention(RetentionPolicy.SOURCE) + @Target 限定语法位置(如METHOD、PARAMETER)
- 由构建工具(如APT处理器)在编译期生成代码 → 必须 @Retention(RetentionPolicy.CLASS) 或 SOURCE,并搭配 @Target 和可选 @Repeatable
- 由运行时框架(如Spring、Hibernate)通过反射解析 → 必须 @Retention(RetentionPolicy.RUNTIME),且 @Target 要覆盖实际使用位置(如TYPE+METHOD+FIELD)
@Target 必须精准,避免过度宽泛
ElementType枚举不是“多选就安全”,而是要最小化授权。例如定义一个用于字段校验的注解:
- ❌ @Target(ElementType.TYPE) —— 允许加在类上,但校验逻辑无法处理类级语义
- ✅ @Target({ElementType.FIELD, ElementType.PARAMETER}) —— 精确匹配字段注入和方法参数两种主流校验入口
- ⚠️ 若需支持局部变量(如Lambda内),需显式添加 ElementType.LOCAL_VARIABLE,但JVM规范不保证所有环境都支持反射获取局部变量注解
@Retention 与 @Inherited/@Documented 的协同约束
这三个元注解存在隐含依赖关系:
- @Inherited 仅对 @Retention(RetentionPolicy.RUNTIME) 且 @Target(ElementType.TYPE) 的类级注解生效;它对方法、字段注解完全无效
- @Documented 只有在 @Retention 为 SOURCE 或 RUNTIME 时才有意义;若设为 CLASS 且未文档化,JavaDoc 工具将跳过该注解
- JSR-269(编译时注解处理)要求注解至少保留到 CLASS 阶段,否则APT处理器无法扫描到
兼容JSR-303/JSR-380校验规范的特别注意点
若注解用于Bean Validation(如@NotBlank、@Size),除基础元注解外,还需满足JSR标准扩展要求:
- 必须声明 @Constraint(validatedBy = ...)(这是JSR-303定义的元注解,非Java内置)
- 注解接口中必须包含 message()、groups()、payload() 三个标准方法,且返回类型固定
- @Target 至少包含 ElementType.FIELD 和 ElementType.METHOD(支持属性和getter校验)
- @Retention 必须为 RUNTIME,因为校验器在运行时执行
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











