第 26 章:注解基础
学习目标
- 知道注解是干什么的
- 会用 JDK 自带注解
- 学会自定义一个注解
一、注解是什么
注解 = 给类 / 方法 / 字段贴标签,框架可以读到这些标签,做相应处理。
java
@Override // 告诉编译器:这是重写
public String toString() { ... }
@Deprecated // 告诉别人:这个方法已过时
public void oldApi() { ... }写代码 99% 时间只需要用别人定义好的注解(
@Override/@Autowired/@RequestMapping), 偶尔需要自己定义一个。
二、JDK 自带注解
2.1 编译相关
java
@Override // 标注方法重写父类,编译期检查
@Deprecated // 标记已过时,调用会有警告
@SuppressWarnings("all") // 抑制警告
@FunctionalInterface // 标记函数式接口(只能有一个抽象方法)2.2 Spring 常用(认识就行)
java
@Component // 标记成 Bean
@Autowired // 自动注入
@RequestMapping("/api") // 映射 URL
@GetMapping("/list") // 处理 GET 请求三、自定义注解
3.1 语法
java
public @interface MyAnno {
// 注解的"属性"用抽象方法表示
String value() default ""; // 默认值
int priority() default 0;
}3.2 使用
java
// 必须给所有没有默认值的属性传值
@MyAnno(value = "hello", priority = 1)
public void test() { }
// 只有 value 时,可以省略名字
@MyAnno("hello")
public void test() { }⚠️ 坑 1:属性名是
value且只有它要赋值时,才能省略value =。java@MyAnno("hello") // ✅ 省略 value @MyAnno(name = "hello") // ❌ 编译报错
四、元注解(修饰注解的注解)
java
@Target(ElementType.METHOD) // 注解能用在哪
@Retention(RetentionPolicy.RUNTIME) // 注解能活多久
@Documented // javadoc 是否包含
@Inherited // 子类是否继承
public @interface MyAnno {
String value() default "";
}4.1 @Target(必须会用)
决定注解能贴在哪些地方:
java
@Target({
ElementType.METHOD, // 方法
ElementType.FIELD, // 字段
ElementType.TYPE, // 类 / 接口
ElementType.PARAMETER, // 方法参数
ElementType.CONSTRUCTOR // 构造方法
})
public @interface MyAnno { }4.2 @Retention(必须会用)
决定注解能活到什么时候:
| 取值 | 存活阶段 | 用途 |
|---|---|---|
SOURCE | 源码期 | 编译后丢弃,class 里没有(@Override) |
CLASS | 编译期 | class 里有,运行时丢弃(默认) |
RUNTIME | 运行时 | 通过反射读取(框架都用这个) |
java
// 框架读取注解 → 必须 RUNTIME
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno { }⚠️ 坑 2:自定义注解默认是
CLASS,反射读不到。Spring 那种"扫到注解才生效"的场景,必须RUNTIME。java@Retention(RetentionPolicy.CLASS) // ❌ 反射读不到 @Retention(RetentionPolicy.RUNTIME) // ✅ 框架读得到
五、读取注解(反射)
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnno {
String value() default "";
}
class User {
@MyAnno("hello")
public void test() { }
}
// 反射读取
Method m = User.class.getDeclaredMethod("test");
MyAnno anno = m.getAnnotation(MyAnno.class);
if (anno != null) {
System.out.println(anno.value()); // "hello"
}六、实战:写一个迷你 @MyTest
模拟 JUnit 的 @Test(找带 @MyTest 的方法,自动执行):
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest { }
// 扫描类里所有带 @MyTest 的方法,全部执行
public class TestRunner {
public static void run(Class<?> clazz) throws Exception {
for (Method m : clazz.getDeclaredMethods()) {
if (m.isAnnotationPresent(MyTest.class)) {
m.setAccessible(true);
System.out.println("执行: " + m.getName());
m.invoke(clazz.getDeclaredConstructor().newInstance());
}
}
}
}
// 用法
class MyTestCases {
@MyTest public void test1() { System.out.println("test1"); }
@MyTest public void test2() { System.out.println("test2"); }
}
// 跑
TestRunner.run(MyTestCases.class);这就是 JUnit / Spring Test 的核心原理。
七、什么时候自定义注解
| 场景 | 自定义注解 |
|---|---|
| 标记需要权限校验 | @RequiresPermission |
| 标记接口返回不需要登录 | @Public |
| 标记日志埋点 | @Log("用户操作") |
| 标记字段映射数据库列 | @Column("user_name") |
套路都是一样:
- 定义注解(
@Retention(RUNTIME)) - 用 AOP / 反射拦截,读取注解做处理
八、本章小结
| 要点 | 关键 |
|---|---|
| 注解 | 贴在类 / 方法 / 字段上的"标签" |
| 自定义 | @interface + 元注解 |
| @Target | 限定能贴的位置 |
| @Retention | 决定能活多久,框架要 RUNTIME |
| value 属性 | 单参数时可省略 value = |
| 读取 | getAnnotation + isAnnotationPresent |
动手练习
- 自定义注解:写一个
@MyLog("操作名"),标在方法上 - 读取注解:写一个工具类,扫描类里所有标了
@MyLog的方法,打印日志名 - Spring 模仿:写
@MyAutowired注解 + 工具,模拟自动注入字段
下一章:第 27 章:Java 进阶路线与资源 →