注解
一、注解的基本概念
1. 定义
注解(Annotation)是 Java SE 5.0 引入的一种特殊语法元素,它以@
符号开头,用于为 Java 代码(类、方法、字段、参数等)添加元数据(描述数据的数据)。这些元数据本身不直接影响代码的执行逻辑,但可以被编译器、工具或运行时环境读取和使用,实现如代码检查、自动生成代码、配置注入等功能。
2. 核心作用
- 编译期检查 :如
@Override
注解,编译器会检查被标注的方法是否真的重写了父类方法,若不存在则报错。 - 生成文档 :配合 Javadoc 工具,
@param
、@return
等注解可自动生成规范的 API 文档。 - 运行时处理 :如 Spring 框架的
@Autowired
,在程序运行时通过反射读取注解,实现依赖注入。 - 代码生成 :如 Lombok 的
@Data
,在编译期自动生成getter
、setter
、toString()
等方法,减少冗余代码。
二、Java 内置注解
Java 提供了 5 个内置的标准注解,直接用于修饰代码元素,无需额外定义:
注解名称 | 作用场景 | 核心功能 |
---|---|---|
@Override |
方法 | 标记方法为重写父类(或实现接口)的方法,编译器校验方法签名是否匹配,避免拼写错误。 |
@Deprecated |
类、方法、字段、参数等 | 标记元素已过时,编译器会对使用该元素的代码发出警告,提醒开发者使用替代方案。 |
@SuppressWarnings |
类、方法、字段等 | 抑制编译器产生的特定警告(需指定警告类型),如@SuppressWarnings("unchecked") 抑制泛型未检查警告。 |
@SafeVarargs |
可变参数方法(JDK 7+) | 标记方法为 "安全的可变参数",抑制编译器对泛型可变参数的 "非安全操作" 警告。 |
@FunctionalInterface |
接口(JDK 8+) | 标记接口为函数式接口(仅含一个抽象方法),编译器校验接口是否符合函数式接口规范,支持 Lambda 表达式。 |
示例:内置注解的使用
import java.util.ArrayList;
import java.util.List;
public class BuiltInAnnotationDemo {
public static void main(String[] args) {
// 使用@Deprecated标记的过时方法
OldClass oldObj = new OldClass();
oldObj.obsoleteMethod(); // 编译器警告:方法已过时
// 使用@SuppressWarnings抑制泛型警告
@SuppressWarnings("unchecked")
List<String> list = new ArrayList(); // 无泛型检查警告
list.add("Java Annotation");
}
}
class OldClass {
// 标记方法已过时,并提示替代方案
@Deprecated(since = "1.2", forRemoval = true)
public void obsoleteMethod() {
System.out.println("This method is obsolete. Use newMethod() instead.");
}
public void newMethod() {
System.out.println("This is the new recommended method.");
}
}
// 标记为函数式接口,仅含一个抽象方法
@FunctionalInterface
interface MyFunctionalInterface {
void doSomething(String msg);
}
三、元注解(Meta-Annotation)
元注解是用于定义其他注解的注解,即 "注解的注解"。Java 提供了 6 个元注解,用于控制自定义注解的生命周期、作用范围等特性:
元注解名称 | 作用 | 可选属性值 |
---|---|---|
@Target |
限定自定义注解可修饰的代码元素类型(如类、方法、字段等) | TYPE (类 / 接口)、METHOD (方法)、FIELD (字段)、PARAMETER (参数)等 |
@Retention |
定义自定义注解的生命周期(即注解信息保留到哪个阶段) | SOURCE (仅源码)、CLASS (源码 + 字节码)、RUNTIME (源码 + 字节码 + 运行时) |
@Documented |
标记自定义注解会被Javadoc 工具提取到 API 文档中(默认不提取) | 无属性,仅作为标记 |
@Inherited |
标记自定义注解可被子类继承(仅对类注解有效,方法 / 字段注解不继承) | 无属性,仅作为标记 |
@Repeatable |
允许自定义注解在同一元素上重复标注(JDK 8+) | 属性值为 "存储该重复注解的容器注解类" |
@Native |
标记注解关联的字段为原生方法(Native Method)依赖的字段(JDK 8+) | 无属性,仅作为标记,较少使用 |
关键元注解详解
1. @Target:控制注解的作用范围
必须指定,否则自定义注解可修饰所有代码元素。示例:
// 限定注解仅可修饰"类/接口"和"方法"
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation {
// 注解属性...
}
2. @Retention:控制注解的生命周期
这是最核心的元注解之一,直接决定注解能否在运行时被读取(如通过反射):
- SOURCE :仅存在于源码中,编译成字节码时被删除(如
@Override
)。 - CLASS:存在于源码和字节码中,但运行时 JVM 不加载,无法通过反射读取(默认值)。
- RUNTIME :存在于源码、字节码和运行时,可通过反射读取(如 Spring 的
@Autowired
)。
示例:
// 注解保留到运行时,支持反射读取
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeAnnotation {
// 注解属性...
}
四、自定义注解
1. 定义语法
自定义注解使用@interface
关键字声明,格式如下:
// 元注解(可选,但@Target和@Retention通常必加)
[元注解1]
[元注解2]
public @interface 注解名称 {
// 注解属性(格式:类型 属性名() [default 默认值];)
类型 属性名1() [default 默认值1];
类型 属性名2() [default 默认值2];
// ...
}
2. 注解属性规则
- 属性类型:只能是基本数据类型(int、boolean 等)、String、Class、枚举(enum)、其他注解或以上类型的数组。
- 默认值:若属性无默认值,使用注解时必须显式赋值;若有默认值,可省略赋值。
- 特殊属性 value :若注解只有一个属性且名为
value
,赋值时可省略 "value=",直接写值;若为数组且只有一个元素,可省略大括号。
3. 自定义注解示例
示例 1:简单的类 / 方法注解
import java.lang.annotation.*;
// 限定注解可修饰类和方法,保留到运行时,支持文档提取
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
// 带默认值的属性
String author() default "Unknown";
// 无默认值的属性(使用时必须赋值)
String version();
// 数组类型属性
String[] tags() default {};
}
// 使用自定义注解修饰类(version必须赋值,author和tags用默认值)
@MyAnnotation(version = "1.0")
class MyClass {
// 使用自定义注解修饰方法(显式赋值所有属性)
@MyAnnotation(author = "Alice", version = "1.1", tags = {"method", "demo"})
public void myMethod() {
System.out.println("This is a method annotated with MyAnnotation.");
}
}
示例 2:支持重复标注的注解(@Repeatable)
import java.lang.annotation.*;
// 1. 定义"重复注解的容器类"
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRepeatableAnnotations {
MyRepeatableAnnotation[] value(); // 存储重复的注解
}
// 2. 定义可重复的注解(指定容器类)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyRepeatableAnnotations.class) // 关键:关联容器类
public @interface MyRepeatableAnnotation {
String value(); // 单个属性value
}
// 3. 使用:同一方法上重复标注
class RepeatableDemo {
// 重复标注(JDK 8+支持)
@MyRepeatableAnnotation("Tag1")
@MyRepeatableAnnotation("Tag2")
@MyRepeatableAnnotation("Tag3")
public void testRepeatable() {
System.out.println("Method with repeated annotations.");
}
}
五、注解的解析(读取与使用)
注解的元数据需要通过 "解析" 才能发挥作用,解析方式分为编译期解析 和运行时解析:
1. 编译期解析
由编译器或注解处理器(Annotation Processor)在编译阶段读取注解,通常用于:
- 代码检查(如
@Override
的语法校验)。 - 自动生成代码(如 Lombok、ButterKnife)。
示例:自定义注解处理器(简化版)
注解处理器需继承AbstractProcessor
,并通过@SupportedAnnotationTypes
指定处理的注解:
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import java.util.Set;
// 声明处理器支持的注解和Java版本
@SupportedAnnotationTypes("com.example.MyAnnotation") // 自定义注解的全类名
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
// 编译时处理注解的核心方法
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 遍历所有被@MyAnnotation标注的元素
for (TypeElement annotation : annotations) {
roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> {
// 读取注解属性
MyAnnotation myAnn = element.getAnnotation(MyAnnotation.class);
String author = myAnn.author();
String version = myAnn.version();
// 输出注解信息(编译时控制台可见)
System.out.printf("Processing %s: author=%s, version=%s%n",
element.getSimpleName(), author, version);
});
}
return true; // 表示注解已被处理,无需其他处理器处理
}
}
2. 运行时解析
通过反射(Reflection) 在程序运行时读取@Retention(RetentionPolicy.RUNTIME)
级别的注解,动态处理逻辑(如 Spring、MyBatis 的核心机制)。
反射解析注解的核心 API
API 方法 | 作用 |
---|---|
Class.getAnnotation(Class) |
获取类上的指定注解 |
Method.getAnnotation(Class) |
获取方法上的指定注解 |
Field.getAnnotation(Class) |
获取字段上的指定注解 |
AnnotatedElement.isAnnotationPresent(Class) |
判断元素是否被指定注解标注 |
示例:运行时反射解析注解
import java.lang.reflect.Method;
public class AnnotationParser {
public static void main(String[] args) throws NoSuchMethodException {
// 1. 解析类上的注解
Class<MyClass> clazz = MyClass.class;
if (clazz.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation classAnn = clazz.getAnnotation(MyAnnotation.class);
System.out.println("Class Annotation Info:");
System.out.printf(" Author: %s%n Version: %s%n Tags: %s%n",
classAnn.author(), classAnn.version(), String.join(",", classAnn.tags()));
}
// 2. 解析方法上的注解
Method method = clazz.getMethod("myMethod");
if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation methodAnn = method.getAnnotation(MyAnnotation.class);
System.out.println("\nMethod Annotation Info:");
System.out.printf(" Author: %s%n Version: %s%n Tags: %s%n",
methodAnn.author(), methodAnn.version(), String.join(",", methodAnn.tags()));
}
// 3. 解析重复注解(需通过容器类获取)
Class<RepeatableDemo> repeatClazz = RepeatableDemo.class;
Method repeatMethod = repeatClazz.getMethod("testRepeatable");
// 方式1:通过容器类获取所有重复注解
MyRepeatableAnnotations container = repeatMethod.getAnnotation(MyRepeatableAnnotations.class);
System.out.println("\nRepeated Annotations Info:");
for (MyRepeatableAnnotation ann : container.value()) {
System.out.printf(" Tag: %s%n", ann.value());
}
// 方式2:直接获取重复注解(JDK 8+支持)
MyRepeatableAnnotation[] repeatAnnotations = repeatMethod.getAnnotationsByType(MyRepeatableAnnotation.class);
for (MyRepeatableAnnotation ann : repeatAnnotations) {
System.out.printf(" Tag (direct): %s%n", ann.value());
}
}
}
六、注解的应用场景
1. 框架开发(最核心场景)
- Spring :
@Autowired
(依赖注入)、@Controller
(标记控制器)、@RequestMapping
(映射请求路径)等。 - MyBatis :
@Mapper
(标记 Mapper 接口)、@Select
(指定查询 SQL)等。 - JUnit :
@Test
(标记测试方法)、@BeforeEach
(测试前执行)等。
2. 减少冗余代码
- Lombok :
@Data
(生成getter
/setter
/toString
)、@NoArgsConstructor
(生成无参构造)、@Slf4j
(注入日志对象)等。 - ButterKnife (已过时,被 ViewBinding 替代):
@BindView
(绑定 XML 控件)。
3. 代码检查与规范
- 自定义注解 + 处理器,校验代码是否符合团队规范(如方法命名必须以
do
开头、字段必须加注释等)。 - 第三方工具如 Checkstyle,通过注解控制代码检查规则。
4. 权限控制与日志记录
- 自定义
@Permission
注解,标记方法需特定权限,运行时通过 AOP(面向切面编程)拦截并校验权限。 - 自定义
@Log
注解,标记方法需记录日志,AOP 拦截方法调用并自动打印入参、返回值、耗时等信息。