注解(Annotation)是 Java 语言中一种特殊的元数据形式,它能在代码中添加标记信息,用于描述类、方法、变量等元素的特性(如权限、过期提示、校验规则等)。除了Java内置的@Override、@Deprecated等注解,开发者还可以根据需求自定义注解,实现更灵活的元数据管理。了解自定义注解的方法及保留策略,是掌握注解应用的核心。
一、Java 自定义注解的方法
自定义注解需使用@interface关键字,本质上是一种特殊的接口,其定义需遵循特定语法,并可通过元注解(描述注解的注解)配置行为。
(一)基本语法与结构
自定义注解的基本格式为:
// 元注解public @interface 注解名 { // 注解属性(可选) 类型 属性名() default 默认值;}
元注解:用于修饰注解的注解,指定注解的保留策略、适用范围等(详见下文 “保留策略”)。
注解属性:类似接口的方法声明,需指定返回类型(基本类型、String、枚举、注解或其数组),可通过default指定默认值。
示例:定义一个用于标记接口版本的注解Version
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;// 元注解:指定注解可用于类和接口@Target({ElementType.TYPE})// 元注解:指定保留策略为运行时@Retention(RetentionPolicy.RUNTIME)public @interface Version { // 主版本号,无默认值(使用时必须指定) int major(); // 次版本号,默认值为0 int minor() default 0; // 版本描述,默认值为"" String description() default "";}
(二)使用自定义注解
定义注解后,可在类、方法、变量等元素上使用,格式为@注解名(属性名=值, ...),若属性有默认值可省略。
示例:使用Version注解标记接口
// 使用注解,指定major,其他属性用默认值@Version(major = 1)public interface UserService { void getUser();}// 完整指定所有属性@Version(major = 2, minor = 1, description = "支持分页查询")public interface OrderService { void getOrders(int pageNum, int pageSize);}
(三)解析自定义注解
注解本身仅为标记,需通过反射机制解析才能发挥作用(如根据注解属性执行特定逻辑)。
示例:通过反射获取类上的Version注解信息
import java.lang.reflect.AnnotatedElement;public class AnnotationParser { public static void parse(Class<?> clazz) { // 判断类是否被Version注解标记 if (clazz.isAnnotationPresent(Version.class)) { // 获取注解实例 Version version = clazz.getAnnotation(Version.class); // 读取注解属性 System.out.println("类名:" + clazz.getSimpleName()); System.out.println("版本:" + version.major() + "." + version.minor()); System.out.println("描述:" + version.description()); } } public static void main(String[] args) { parse(UserService.class); // 解析UserService接口的注解 parse(OrderService.class); // 解析OrderService接口的注解 }}
解析结果会输出两个接口的版本信息,实现了通过注解传递元数据并动态处理的功能。
二、注解的保留策略
注解的保留策略(Retention Policy)决定了注解在代码生命周期(源码、编译期、运行时)的保留阶段,通过元注解@Retention指定,有以下三种类型:
(一)SOURCE:仅保留在源码中
注解仅在 Java 源文件中存在,编译成字节码(.class 文件)时被丢弃,不参与运行时。
适用场景:用于编译器检查或代码生成,如@Override(检查方法重写)、Lombok 的@Data(编译时生成 getter/setter)。
示例:定义 SOURCE 级别的注解
import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;@Retention(RetentionPolicy.SOURCE)public @interface Debug { String value() default "调试信息";}
该注解仅在源码中可见,编译后消失,无法通过反射获取。
(二)CLASS:保留到字节码中(默认策略)
注解被编译到字节码中,但在 JVM 加载类时不被保留,运行时无法通过反射访问。
适用场景:用于字节码增强工具(如 AspectJ)在编译后、运行前修改字节码,实现特定功能(如日志切入)。
示例:定义 CLASS 级别的注解
import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;@Retention(RetentionPolicy.CLASS)public @interface Log { String action();}
该注解存在于.class 文件中,但运行时反射无法获取,需通过字节码工具处理。
(三)RUNTIME:保留到运行时
注解被保留到 JVM 运行阶段,可通过反射机制动态获取注解信息,是最常用的保留策略。
适用场景:用于运行时动态逻辑处理,如 Spring 的@Controller(IOC 容器扫描)、JUnit 的@Test(测试方法识别)。
前面定义的Version注解即为 RUNTIME 级别,可在运行时通过反射解析,实现灵活的业务逻辑。
三、自定义注解的常见应用场景
代码标记与说明:如用@Deprecated标记过时方法,自定义@Todo标记待完成功能。
配置与参数校验:如用@NotNull标记必填参数,通过解析注解实现参数合法性检查。
框架与工具支持:如 Spring 通过@Autowired实现依赖注入,MyBatis 通过@Select映射 SQL 语句。
编译期检查:如自定义@NonEmpty注解,结合编译器插件检查集合是否为空。
四、自定义注解的注意事项
属性类型限制:注解属性的返回类型只能是基本类型、String、枚举、注解或其数组,不能是普通类。
默认值规则:若属性无默认值,使用注解时必须显式指定值;数组类型的属性赋值需用{}(如values = {1, 2})。
元注解配合:除@Retention外,常用元注解还有@Target(指定注解可修饰的元素类型,如类、方法)、@Inherited(允许子类继承父类的注解)、@Documented(生成 API 文档时包含注解)。
自定义注解是 Java 中实现元编程的重要手段,通过合理定义和解析注解,能显著提升代码的灵活性和可维护性。选择合适的保留策略是关键:源码级注解用于编译检查,字节码级注解用于字节码增强,运行时注解用于动态逻辑处理。掌握自定义注解的方法,能帮助开发者更好地理解框架原理(如 Spring、MyBatis),并构建更优雅的代码结构。