什么是 Java 注解?
注解是 Java 提供的一种元数据(Metadata)机制,元数据是“关于数据的数据”,它不属于程序本身逻辑的一部分,但可以用来对代码进行说明、配置或约束。

你可以把它想象成给代码贴上的“标签”或“注释”,但这些标签可以被编译器、工具程序或运行时的虚拟机读取,并据此执行特定的操作。
核心思想
- 它不是代码:注解本身不包含任何业务逻辑。
- 它是一种标记:它标记了类、方法、字段、参数等,告诉其他工具“我是一个什么类型的组件”或“我需要某种特殊处理”。
- 它需要被处理:注解本身是“死的”,必须配合相应的“处理器”(Processor)才能发挥价值,这个处理器可以是编译器、IDE、运行时环境或一个独立的程序。
注解的语法
定义注解
使用 @interface 关键字来定义一个注解。
// 定义一个名为 MyAnnotation 的注解
public @interface MyAnnotation {
// 注解的成员(看起来像方法)
String value(); // 成员名为 value,类型为 String
int count() default 0; // 成员名为 count,类型为 int,并有一个默认值 0
}
- 成员:注解中的“方法”被称为成员,成员的类型只能是基本类型、String、Class、枚举、注解,以及这些类型的数组。
- 默认值:可以使用
default关键字为成员指定默认值,如果一个成员有默认值,那么在使用该注解时就可以不提供该成员的值。 - value 成员:如果注解只有一个名为
value的成员,那么在使用时可以直接赋值,而不需要写成value = "xxx"的形式。
使用注解
在需要的地方使用 符号来应用注解。
@MyAnnotation(value = "TestClass", count = 10)
public class TestClass {
@MyAnnotation("testMethod")
public void testMethod() {
// ...
}
}
- 在上面的例子中,
TestClass类被标记为@MyAnnotation,并提供了value和count的值。 testMethod方法被标记为@MyAnnotation,由于它只提供了value成员,所以可以简写。
内置注解
Java 提供了一些内置的注解,用于在编译和运行时进行特殊处理。

| 注解 | 作用 | 例子 |
|---|---|---|
@Override |
告诉编译器,这个方法覆写了父类的方法,如果拼写错误或方法签名不匹配,编译器会报错。 | @Override public String toString() { ... } |
@Deprecated |
标记一个方法、类或字段为“过时的”,不推荐使用,使用这些元素时,编译器会给出警告。 | @Deprecated public void oldMethod() { ... } |
@SuppressWarnings |
抑制编译器产生的特定警告。 | @SuppressWarnings("unchecked") |
@SafeVarargs |
告诉编译器,这个可变参数方法是类型安全的,可以抑制“堆污染”相关的警告。 | @SafeVarargs static <T> List<T> asList(T... a) { ... } |
元注解
元注解是“注解的注解”,它们用来修饰注解的定义,控制注解的行为。
| 元注解 | 作用 |
|---|---|
@Target |
指定被修饰的注解可以应用在哪些程序元素上。ElementType.TYPE(类、接口、枚举),ElementType.METHOD(方法),ElementType.FIELD(字段)等。 |
@Retention |
指定被修饰的注解的生命周期,有三个级别: 1. RetentionPolicy.SOURCE:只在源码中存在,编译时被丢弃。2. RetentionPolicy.CLASS:在 .class 文件中存在,但运行时被 JVM 丢弃(默认级别)。3. RetentionPolicy.RUNTIME:在运行时也存在,可以通过反射读取。(最常用) |
@Documented |
指定该注解会被包含在 JavaDoc 文档中。 |
@Inherited |
指定被修饰的注解具有继承性,如果一个类被 @Inherited 注解的注解标记,那么它的子类也会自动拥有这个注解。 |
示例:使用元注解定义一个注解
import java.lang.annotation.*;
// 1. @Target: 这个注解只能用在类和方法上
// 2. @Retention: 这个注解在运行时仍然可用,可以通过反射读取
// 3. @Documented: 这个注解会出现在 Javadoc 中
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
String value() default "";
int count() default 0;
}
注解的处理器
这是注解发挥威力的关键,处理器读取注解信息并执行相应操作。
编译时处理器
这是最常见的一种处理器,它在代码编译阶段执行,用于生成额外的代码(如 getter/setter、equals/hashCode)或进行代码检查。
经典例子:Lombok

Lombok 是一个通过注解来简化 Java 代码的库,你只需要在类上添加几个注解,Lombok 的编译时处理器就会在编译时自动生成相应的代码(如 @Getter, @Setter, @ToString, @Data 等)。
import lombok.Data;
@Data // Lombok 的注解,编译时会自动生成 getter, setter, toString, equals, hashCode
public class User {
private String name;
private int age;
}
// 编译后,User.class 文件中会包含以下方法(虽然你看不到源码):
// public String getName() { ... }
// public void setName(String name) { ... }
// public String toString() { ... }
// ...
运行时处理器
在程序运行时,通过反射机制读取注解信息,并据此执行逻辑。
经典例子:JUnit 5
JUnit 框架就是通过运行时注解来识别和执行测试用例的。
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MyTest {
@Test // JUnit 的注解,告诉测试运行器这是一个测试方法
public void testAddition() {
int result = 2 + 2;
assertEquals(4, result); // 断言
}
}
当 JUnit 测试运行器启动时,它会通过反射扫描你的测试类,找到所有被 @Test 注解标记的方法,然后逐一执行它们。
运行时处理器(Spring Framework)
Spring 框架大量使用注解来实现其“控制反转”和“依赖注入”的核心思想。
import org.springframework.stereotype.Component;
@Component // Spring 的注解,告诉 Spring "我是一个 Bean,请把我管理起来"
public class MyService {
public void doSomething() {
System.out.println("Service is doing something.");
}
}
当 Spring 应用启动时,它的容器会扫描被 @Component(或 @Service, @Repository 等)注解标记的类,将它们实例化并放入容器中,以便在其他地方通过 @Autowired 注入使用。
自定义一个运行时处理器示例
让我们创建一个简单的例子,定义一个注解,然后在运行时通过反射读取它。
第 1 步:定义注解
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) // 必须是 RUNTIME,否则反射读不到
@Target(ElementType.METHOD)
public @interface MyAction {
String name();
}
第 2 步:在代码中使用注解
public class MyController {
@MyAction(name = "sayHello")
public void hello() {
System.out.println("Hello, World!");
}
@MyAction(name = "sayGoodbye")
public void goodbye() {
System.out.println("Goodbye, World!");
}
}
第 3 步:编写处理器(通过反射读取)
import java.lang.reflect.Method;
public class AnnotationProcessor {
public static void main(String[] args) throws Exception {
// 1. 获取 MyController 的 Class 对象
Class<?> clazz = MyController.class;
// 2. 获取所有声明的方法
Method[] methods = clazz.getDeclaredMethods();
// 3. 遍历方法,查找带有 @MyAction 注解的方法
for (Method method : methods) {
// 检查方法上是否有 @MyAction 注解
if (method.isAnnotationPresent(MyAction.class)) {
// 4. 获取注解实例
MyAction action = method.getAnnotation(MyAction.class);
// 5. 读取注解的属性
String actionName = action.name();
System.out.println("Found action: " + actionName);
// 6. 调用被注解的方法
// 需要先创建对象实例,因为方法是实例方法
MyController controller = (MyController) clazz.getDeclaredConstructor().newInstance();
method.invoke(controller);
}
}
}
}
运行结果
Found action: sayHello
Hello, World!
Found action: sayGoodbye
Goodbye, World!
| 特性 | 描述 |
|---|---|
| 本质 | 一种元数据,用于为代码添加信息,而不是逻辑。 |
| 语法 | @interface 定义, 使用。 |
| 分类 | 内置注解(@Override等)、元注解(@Target等)、自定义注解。 |
| 生命周期 | 由 @Retention 控制,分为 SOURCE、CLASS、RUNTIME。 |
| 核心价值 | 简化代码(如 Lombok)、提供框架支持(如 Spring、JUnit)、实现编译时检查。 |
| 工作原理 | 通过处理器在编译时或运行时读取注解信息,并执行相应操作,处理器通常依赖反射(运行时)或Java Compiler API(编译时)。 |
理解注解是掌握现代 Java 框架和工具的关键一步,它极大地提高了 Java 代码的灵活性和可维护性。
