0%

注解

Java 注解(Annotation)详解:从元注解到注解处理器

注解(Annotation)是 Java 5 引入的一种元数据(metadata)机制,用于为代码添加额外信息(如配置、约束、说明等),不直接影响代码逻辑,但可通过工具(编译器、框架)解析并产生实际作用(如编译检查、代码生成)。本文将从基础概念、元注解、注解处理器等方面全面解析 Java 注解。

注解的基本概念

什么是注解?

注解是一种特殊的 “标记”,可附加在类、方法、字段等程序元素上,格式为 @注解名(属性=值)。例如:

1
2
3
4
5
6
7
// 方法上的@Override注解(标记方法重写)
@Override
public void run() { ... }

// 类上的@Deprecated注解(标记类已过时)
@Deprecated
public class OldClass { ... }

注解的作用

  • 编译检查:如 @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
2
3
4
5
// 元注解@Target指定注解可用于方法和字段
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface Log {
String value() default ""; // 注解属性
}

@Retention:指定注解的生命周期

用于定义注解的保留阶段(即注解信息在何时可见),属性为 RetentionPolicy,取值:

RetentionPolicy 取值 含义 典型应用
SOURCE 仅在源码中保留,编译器编译时丢弃 编译检查(如 @Override
CLASS 保留到 class 文件中,JVM 运行时丢弃 字节码增强(如类加载时修改代码)
RUNTIME 保留到运行时,JVM 可通过反射获取 框架运行时解析(如 @Autowired

示例:定义一个运行时可见的注解

1
2
3
4
5
6
// 元注解@Retention指定注解在运行时可见
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
int id();
String name();
}

@Documented:标记注解可被 javadoc 文档包含

默认情况下,注解不会出现在 javadoc 文档中。添加 @Documented 后,注解将被包含在生成的文档中,增强文档可读性。

1
2
3
4
5
@Documented // 标记该注解会被javadoc记录
@Target(ElementType.TYPE)
public @interface Component {
String value() default "";
}

@Inherited:标记注解可被子类继承

默认情况下,父类的注解不会被子类继承。添加 @Inherited 后,子类会继承父类上的该注解(仅适用于类级别注解)。

1
2
3
4
5
6
7
8
9
10
11
@Inherited // 允许子类继承该注解
@Target(ElementType.TYPE)
public @interface MyInheritedAnnotation {
}

// 父类使用注解
@MyInheritedAnnotation
public class Parent { ... }

// 子类会继承@MyInheritedAnnotation
public class Child extends Parent { ... }

@Repeatable(Java 8+):允许注解重复使用

默认情况下,一个程序元素上不能重复使用同一注解。@Repeatable 允许同一注解在同一元素上多次使用,需指定一个 “容器注解” 存储重复的注解实例。

示例:定义可重复的注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 容器注解:存储重复的@Tag注解
@Target(ElementType.TYPE)
public @interface Tags {
Tag[] value(); // 数组类型,存储重复的注解
}

// 可重复注解:用@Repeatable指定容器注解
@Repeatable(Tags.class)
public @interface Tag {
String name();
}

// 重复使用@Tag注解
@Tag(name = "java")
@Tag(name = "annotation")
public class Article { ... }

自定义注解:创建自己的注解

自定义注解需使用 @interface 关键字,本质是继承 java.lang.annotation.Annotation 的接口。注解可包含属性(类似接口的方法),属性支持默认值。

自定义注解的基本格式

1
2
3
4
5
6
7
8
// 元注解:指定适用范围和生命周期
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Validate {
// 注解属性(类型支持:基本类型、String、Class、枚举、注解、数组)
boolean required() default true; // 带默认值的属性
String message() default "值不合法"; // 默认值
}

使用自定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
public class User {
@Validate(required = true, message = "用户名不能为空")
private String username;

@Validate(required = false) // 使用默认message
private int age;

// 方法上使用注解
@Validate
public void setUsername(String username) {
this.username = username;
}
}

注解的处理方式

根据 @Retention 声明的生命周期,注解的处理方式分为三类:

源码期处理(SOURCE

仅在源码中保留,由编译器或注解处理器(Processor)解析,用于生成代码或编译检查(如 Lombok、Dagger)。

  • 特点:不进入 class 文件,无运行时开销。
  • 工具:需通过注解处理器(自定义 Processor)处理。

类加载期处理(CLASS

保留到 class 文件,由类加载器或字节码增强工具(如 ASM)解析,用于修改字节码(如热部署、AOP 增强)。

  • 特点:不进入运行时,需字节码操作工具支持。

运行期处理(RUNTIME

保留到运行时,可通过反射 API(java.lang.reflect)获取注解信息,用于框架动态逻辑(如 Spring 注解)。

示例:通过反射解析运行时注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class AnnotationProcessor {
public static void validate(Object obj) throws IllegalAccessException {
// 获取类的所有字段
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
// 检查字段是否有@Validate注解
if (field.isAnnotationPresent(Validate.class)) {
Validate validate = field.getAnnotation(Validate.class);
field.setAccessible(true); // 允许访问私有字段
Object value = field.get(obj);

// 根据注解属性执行校验逻辑
if (validate.required() && value == null) {
throw new IllegalArgumentException(validate.message());
}
}
}
}

public static void main(String[] args) throws IllegalAccessException {
User user = new User();
user.setUsername(null); // 用户名未赋值
validate(user); // 抛出异常:用户名不能为空
}
}

注解处理器(Annotation Processor)

注解处理器是编译期处理 SOURCECLASS 生命周期注解的工具,本质是一个继承 AbstractProcessor 的类,在编译时扫描并处理注解,可用于自动生成代码、校验注解合法性等(如 ButterKnife 自动生成视图绑定代码)。

注解处理器的核心要素

  • 继承 AbstractProcessor:重写 process 方法处理注解。
  • 注册处理器:通过 META-INF/services 文件声明处理器,让编译器识别。
  • 依赖工具类:通过 ProcessingEnvironment 获取 Elements(元素工具)、Filer(文件生成工具)等。

实现自定义注解处理器的步骤

步骤 1:定义注解(SOURCE 生命周期)
1
2
3
4
5
@Target(ElementType.CLASS)
@Retention(RetentionPolicy.SOURCE) // 仅源码期保留
public @interface GenerateService {
String value(); // 服务名称
}
步骤 2:实现注解处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;

// 声明支持的注解和Java版本
@SupportedAnnotationTypes("com.example.GenerateService") // 注解全类名
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class ServiceProcessor extends AbstractProcessor {

private Filer filer; // 用于生成Java文件的工具

@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
filer = env.getFiler(); // 初始化文件生成工具
}

// 核心方法:处理注解
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 遍历所有被@GenerateService注解的元素
for (TypeElement annotation : annotations) {
roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> {
// 解析注解属性
GenerateService service = element.getAnnotation(GenerateService.class);
String serviceName = service.value();
String className = element.getSimpleName().toString() + "Service";

// 生成Java代码(如生成服务实现类)
try (Writer writer = filer.createSourceFile("com.example." + className).openWriter()) {
writer.write("package com.example;\n");
writer.write("public class " + className + " {\n");
writer.write(" public void execute() {\n");
writer.write(" System.out.println(\"" + serviceName + " 执行\");\n");
writer.write(" }\n");
writer.write("}\n");
} catch (IOException e) {
e.printStackTrace();
}
});
}
return true; // 表示注解已处理完毕
}
}
步骤 3:注册处理器

编译器需要知道处理器的存在,需通过以下方式注册:

  1. 在项目 resources 目录下创建路径:META-INF/services

  2. 在该路径下创建文件javax.annotation.processing.Processor,内容为处理器的全类名:

    1
    com.example.ServiceProcessor
步骤 4:配置 Maven 插件(避免编译冲突)

注解处理器需独立于主项目编译,否则可能引发循环依赖。在处理器模块的 pom.xml 中添加:

1
2
3
4
5
6
7
8
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>-proc:none</compilerArgument> <!-- 禁止编译时处理注解 -->
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
步骤 5:使用注解并验证

在主项目中使用 @GenerateService 注解:

1
2
@GenerateService("用户服务")
public class User { ... }

编译主项目后,处理器会自动生成 UserService.java 文件,内容为步骤 2 中定义的代码。

注解处理器的应用场景

  • 自动生成模板代码:如 Lombok 生成 getter/setter,减少手动编写。
  • 框架注解处理:如 Dagger 生成依赖注入代码,提高运行时效率。
  • 编译期校验:如检查注解属性合法性,提前发现错误

欢迎关注我的其它发布渠道