Java元注解并非用于“炫技”,而是让自定义注解真正具备可配置、可复用、可被正确解析的能力。如果元注解使用不当,自定义注解仅仅是一个标签;而合理运用后,它就能成为业务逻辑的声明式入口。
先明确四大核心元注解各自负责的领域。
@Target 指定注解可以标注的位置——是类、方法、参数,还是连泛型参数都不放过?日常开发中 ElementType.METHOD 和 ElementType.TYPE 最常用,但不要忽略 ElementType.PARAMETER(用于参数校验)和 ElementType.TYPE_PARAMETER(泛型类型占位符),它们在框架扩展中越来越关键。
@Retention 控制注解的生命周期。SOURCE 仅在编译期生效(如 @Override);CLASS 是默认值,注解会进入字节码但不会被 JVM 加载;RUNTIME 才能让反射在运行时读取——只要需要反射解析注解(例如 Spring AOP 或自定义拦截器),就必须设为 RUNTIME,这个细节很容易踩坑。
@Documented 能让注解出现在生成的 Javadoc 中。它不改变运行时行为,但对团队协作非常实用:别人查看接口文档时,一眼就能看到 @NonNull、@Deprecated 等语义信息,省去了反复翻代码的麻烦。
@Inherited 允许子类继承父类的注解——但注意,它只作用于类级别的注解,并且只适用于 extends 关系,不适用于接口实现或方法重写。换句话说,父类方法上的 @Inherited 注解,子类方法并不会自动继承,这个点常被误解。

单个元注解能力有限,组合使用才能贴近真实场景。举个例子,定义一个权限检查注解,你会怎么配置?大致如下:
@Target({ElementType.METHOD, ElementType.TYPE})—— 支持标注在控制器类或具体接口方法上@Retention(RetentionPolicy.RUNTIME)—— 运行时通过 AOP 切面读取并执行鉴权@Documented—— 在 Swagger 或 Javadoc 中显式暴露权限要求- 不加
@Inherited—— 权限策略通常需要显式声明,避免子类意外继承宽松策略
再比如一个配置绑定注解(类似 Spring 的 @ConfigurationProperties):
@Target(ElementType.TYPE)—— 只允许标注在配置类上@Retention(RetentionPolicy.RUNTIME)—— 需要在启动时扫描并注入属性值@Repeatable(PropertySources.class)—— 允许同一个类上多次声明不同配置源,增强灵活性
说到 @Repeatable,有两个隐性约束需要留意。
要让注解支持重复声明,不能只加 @Repeatable,必须同时满足:
- 被标记的注解(如
@MyRole)需要声明@Repeatable(MyRoles.class),其中MyRoles是容器注解 - 容器注解
MyRoles必须有value()方法,且返回类型是该注解类型的数组(如MyRole[])
否则编译直接报错:“Repeatable annotation type must have a containing annotation type”。Spring 5+ 已经广泛使用这个机制(例如 @Bean、@Scope),但手写时容易遗漏容器注解的规范定义。
最后,也是很多人容易犯的错:不要把元注解当作功能开关。
元注解本身不执行逻辑——@Target 不会阻止你将注解写在错误位置(IDE 或编译器才会检查),@Retention(RUNTIME) 也不等于“自动生效”。真正起作用的是你编写的处理器:可能是一个 Aspect、一个 BeanPostProcessor、或者一段反射遍历加条件判断的代码。
典型反例:
- 定义了
@LogExecutionTime并标注了@Retention(RUNTIME),却没有编写 AOP 切面——注解存在,但日志永远不会打印 - 给字段添加了
@NotBlank,但未引入 Hibernate Validator 或未触发校验流程——空字符串照样入库
记住:元注解是“说明书”,不是“执行器”。声明之后,务必配套实现解析与响应逻辑。
不复杂,但容易忽略。
