Java 注解(Annotation)详解:从元注解到注解处理器
注解(Annotation)是 Java 5 引入的一种元数据(metadata)机制,用于为代码添加额外信息(如配置、约束、说明等),不直接影响代码逻辑,但可通过工具(编译器、框架)解析并产生实际作用(如编译检查、代码生成)。本文将从基础概念、元注解、注解处理器等方面全面解析 Java 注解。
注解的基本概念
什么是注解?
注解是一种特殊的 “标记”,可附加在类、方法、字段等程序元素上,格式为 @注解名(属性=值)。例如:
1 | // 方法上的@Override注解(标记方法重写) |
注解的作用
- 编译检查:如
@Override确保方法正确重写父类方法,编译器会校验正确性。 - 代码生成:如 Lombok 的
@Data自动生成 getter/setter 方法,减少模板代码。 - 运行时处理:如 Spring 的
@Autowired实现依赖注入,框架在运行时通过反射解析注解。 - 文档说明:如
@Deprecated标记过时元素,javadoc 会包含该信息。
元注解:注解的注解
元注解(Meta Annotation)是用于修饰 “注解” 的注解,定义了注解的适用范围、生命周期、继承性等基本特性。Java 内置了 5 种元注解:
@Target:指定注解的适用位置
用于限制注解可附加的程序元素(如类、方法、字段等),属性为 ElementType 数组,常见取值:
ElementType 取值 |
含义 | 示例 |
|---|---|---|
TYPE |
类、接口、枚举 | @Service 用于类 |
METHOD |
方法 | @Override 用于方法 |
FIELD |
字段(成员变量) | @Autowired 用于字段 |
PARAMETER |
方法参数 | @RequestParam 用于参数 |
CONSTRUCTOR |
构造器 | 自定义注解用于构造器 |
LOCAL_VARIABLE |
局部变量 | 较少使用 |
ANNOTATION_TYPE |
注解类型本身 | 元注解修饰其他注解 |
PACKAGE |
包 | 用于包信息配置 |
TYPE_PARAMETER(Java 8+) |
类型参数(如泛型 <T>) |
@NonNull <T> T get() |
TYPE_USE(Java 8+) |
任何类型使用处(如变量声明) | List<@NonNull String> |
示例:定义一个仅用于方法和字段的注解
1 | // 元注解@Target指定注解可用于方法和字段 |
@Retention:指定注解的生命周期
用于定义注解的保留阶段(即注解信息在何时可见),属性为 RetentionPolicy,取值:
RetentionPolicy 取值 |
含义 | 典型应用 |
|---|---|---|
SOURCE |
仅在源码中保留,编译器编译时丢弃 | 编译检查(如 @Override) |
CLASS |
保留到 class 文件中,JVM 运行时丢弃 | 字节码增强(如类加载时修改代码) |
RUNTIME |
保留到运行时,JVM 可通过反射获取 | 框架运行时解析(如 @Autowired) |
示例:定义一个运行时可见的注解
1 | // 元注解@Retention指定注解在运行时可见 |
@Documented:标记注解可被 javadoc 文档包含
默认情况下,注解不会出现在 javadoc 文档中。添加 @Documented 后,注解将被包含在生成的文档中,增强文档可读性。
1 | // 标记该注解会被javadoc记录 |
@Inherited:标记注解可被子类继承
默认情况下,父类的注解不会被子类继承。添加 @Inherited 后,子类会继承父类上的该注解(仅适用于类级别注解)。
1 | // 允许子类继承该注解 |
@Repeatable(Java 8+):允许注解重复使用
默认情况下,一个程序元素上不能重复使用同一注解。@Repeatable 允许同一注解在同一元素上多次使用,需指定一个 “容器注解” 存储重复的注解实例。
示例:定义可重复的注解
1 | // 容器注解:存储重复的@Tag注解 |
自定义注解:创建自己的注解
自定义注解需使用 @interface 关键字,本质是继承 java.lang.annotation.Annotation 的接口。注解可包含属性(类似接口的方法),属性支持默认值。
自定义注解的基本格式
1 | // 元注解:指定适用范围和生命周期 |
使用自定义注解
1 | public class User { |
注解的处理方式
根据 @Retention 声明的生命周期,注解的处理方式分为三类:
源码期处理(SOURCE)
仅在源码中保留,由编译器或注解处理器(Processor)解析,用于生成代码或编译检查(如 Lombok、Dagger)。
- 特点:不进入 class 文件,无运行时开销。
- 工具:需通过注解处理器(自定义
Processor)处理。
类加载期处理(CLASS)
保留到 class 文件,由类加载器或字节码增强工具(如 ASM)解析,用于修改字节码(如热部署、AOP 增强)。
- 特点:不进入运行时,需字节码操作工具支持。
运行期处理(RUNTIME)
保留到运行时,可通过反射 API(java.lang.reflect)获取注解信息,用于框架动态逻辑(如 Spring 注解)。
示例:通过反射解析运行时注解
1 | public class AnnotationProcessor { |
注解处理器(Annotation Processor)
注解处理器是编译期处理 SOURCE 或 CLASS 生命周期注解的工具,本质是一个继承 AbstractProcessor 的类,在编译时扫描并处理注解,可用于自动生成代码、校验注解合法性等(如 ButterKnife 自动生成视图绑定代码)。
注解处理器的核心要素
- 继承
AbstractProcessor:重写process方法处理注解。 - 注册处理器:通过
META-INF/services文件声明处理器,让编译器识别。 - 依赖工具类:通过
ProcessingEnvironment获取Elements(元素工具)、Filer(文件生成工具)等。
实现自定义注解处理器的步骤
步骤 1:定义注解(SOURCE 生命周期)
1 |
|
步骤 2:实现注解处理器
1 | import javax.annotation.processing.*; |
步骤 3:注册处理器
编译器需要知道处理器的存在,需通过以下方式注册:
在项目
resources目录下创建路径:META-INF/services。在该路径下创建文件javax.annotation.processing.Processor,内容为处理器的全类名:
1
com.example.ServiceProcessor
步骤 4:配置 Maven 插件(避免编译冲突)
注解处理器需独立于主项目编译,否则可能引发循环依赖。在处理器模块的 pom.xml 中添加:
1 | <plugin> |
步骤 5:使用注解并验证
在主项目中使用 @GenerateService 注解:
1 |
|
编译主项目后,处理器会自动生成 UserService.java 文件,内容为步骤 2 中定义的代码。
注解处理器的应用场景
- 自动生成模板代码:如 Lombok 生成 getter/setter,减少手动编写。
- 框架注解处理:如 Dagger 生成依赖注入代码,提高运行时效率。
- 编译期校验:如检查注解属性合法性,提前发现错误