Skip to content
第 26 章 后端 ⏱ 6 分钟阅读

第 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")

套路都是一样:

  1. 定义注解(@Retention(RUNTIME))
  2. 用 AOP / 反射拦截,读取注解做处理

八、本章小结 ​

要点关键
注解贴在类 / 方法 / 字段上的"标签"
自定义@interface + 元注解
@Target限定能贴的位置
@Retention决定能活多久,框架要 RUNTIME
value 属性单参数时可省略 value =
读取getAnnotation + isAnnotationPresent

动手练习 ​

  1. 自定义注解:写一个 @MyLog("操作名"),标在方法上
  2. 读取注解:写一个工具类,扫描类里所有标了 @MyLog 的方法,打印日志名
  3. Spring 模仿:写 @MyAutowired 注解 + 工具,模拟自动注入字段

下一章:第 27 章:Java 进阶路线与资源 →

本站基于 VitePress 构建 · 由 StackHub 团队维护