什麼是註解?
Annotation(註解) 是一種元數據,提供「關於程序的程序級信息」,不直接影響業務邏輯執行。三大用途:
- 編譯器提示:錯誤檢查、抑制警告(
@Override、@SuppressWarnings)
- 編譯時/部署時處理:代碼生成、配置文件產出(Lombok、MapStruct、MyBatis Generator)
- 運行時反射讀取:框架自動裝配、切面攔截(Spring @Autowired、JPA @Entity、JUnit @Test)
基礎語法
// 標記型(無元素)
@Override
@Deprecated
// 單元素(值名稱可省略為 value)
@SuppressWarnings("unchecked")
@MyAnnotation("singleValue")
// 多元素
@MyAnnotation(name = "test", version = 1, tags = {"a", "b"})
// Java 8+ 重複註解
@Schedule(dayOfWeek = "Mon")
@Schedule(dayOfWeek = "Wed")
public void run() { }
註解可用位置(Java 8+ 擴展為類型註解)
| 位置 | 範例 |
| 類/介面/枚舉聲明 | @Entity class User {} |
| 字段/方法/構造器/參數/局部變量 | @Autowired private UserService svc; |
| 類型使用(Java 8 新增) | new @NonNull String[]、(@NonNull String) obj、List<@Email String>、throws @Critical IOException |
JDK 內建註解
java.lang 基礎註解
| 註解 | 作用 | 適用目標 |
@Override | 檢查是否正確重寫父類方法 | 方法 |
@Deprecated | 標記棄用,編譯器警告 | 類/方法/字段/構造器/包/參數 |
@SuppressWarnings | 抑制指定編譯器警告 | 所有 |
@SafeVarargs | 抑制可變參數堆污染警告(Java 7+) | 方法/構造器 |
@FunctionalInterface | 標記函數式介面,編譯器校驗單一抽象方法(Java 8+) | 介面 |
java.lang.annotation 元註解
| 元註解 | 作用 | 關鍵屬性 |
@Retention | 生命週期 | SOURCE(源碼)、CLASS(字節碼,默認)、RUNTIME(運行時可反射) |
@Target | 允許修飾的程序元素 | ElementType.TYPE/FIELD/METHOD/PARAMETER/CONSTRUCTOR/LOCAL_VARIABLE/ANNOTATION_TYPE/PACKAGE/TYPE_PARAMETER/TYPE_USE |
@Documented | 包含在 Javadoc 中 | 無 |
@Inherited | 子類自動繼承父類該註解(僅限 TYPE) | 無 |
@Repeatable | 允許同一位置重複使用(Java 8+) | value = ContainerAnnotation.class |
自定義註解實戰
1. 運行時反射讀取(框架常用)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface ApiOperation {
String value() default "";
String httpMethod() default "GET";
int order() default 0;
}
// 使用
@ApiOperation(value = "用戶登錄", httpMethod = "POST", order = 1)
public Response login(@RequestBody LoginReq req) { ... }
// 反射讀取
Method m = UserController.class.getMethod("login", LoginReq.class);
ApiOperation op = m.getAnnotation(ApiOperation.class);
System.out.println(op.value() + " | " + op.httpMethod());
2. 編譯時處理(APT,如 Lombok)
// 註解定義
@Retention(RetentionPolicy.SOURCE) // 僅源碼期保留
@Target(ElementType.TYPE)
public @interface GenerateBuilder { }
// 處理器(繼承 AbstractProcessor)
@SupportedAnnotationTypes("com.example.GenerateBuilder")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class BuilderProcessor extends AbstractProcessor {
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
for (TypeElement te : env.getElementsAnnotatedWith(GenerateBuilder.class)) {
// 生成 XXXBuilder.java 寫入 processingEnv.getFiler()
}
return true;
}
}
// META-INF/services/javax.annotation.processing.Processor 註冊處理器類名
3. 重複註解容器模式
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(Schedules.class) // 指定容器註解
public @interface Schedule {
String dayOfWeek();
String time();
}
// 容器註解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Schedules {
Schedule[] value();
}
// 使用
@Schedule(dayOfWeek = "Mon", time = "10:00")
@Schedule(dayOfWeek = "Fri", time = "15:00")
public void backup() { }
// 讀取(Java 8+ 自動合成容器)
Method m = BackupService.class.getMethod("backup");
Schedule[] arr = m.getAnnotationsByType(Schedule.class); // 直接獲取數組
// 或手動獲取容器
Schedules container = m.getAnnotation(Schedules.class);
類型註解與可插拔類型系統
Java 8 引入 TYPE_USE、TYPE_PARAMETER,配合 Checker Framework 實現編譯期強類型檢查:
// 非空檢查
public void setName(@NonNull String name) { ... }
// 正則檢查
public void setEmail(@Regex("[^@]+@[^@]+") String email) { }
// 不可變集合
List<@Readonly String> immutableList = ...;
// 編譯命令
javac -processor org.checkerframework.checker.nullness.NullnessChecker MyClass.java
反射 API 讀取註解(Java 8+ 增強)
| 方法 | 行為 | 重複註解處理 |
getAnnotation(Class<T>) | 返回單個註解(兼容模式:若有重複返回容器) | 返回容器註解 |
getAnnotationsByType(Class<T>) | 返回所有指定類型註解數組(含重複) | 自動展開容器,直接拿數組 |
getDeclaredAnnotationsByType(Class<T>) | 不包含繼承的註解 | 同上 |
總結
- 註解三大階段:**SOURCE → CLASS → RUNTIME**,按需選擇
@Retention。
@Target 精確限制使用位置,Java 8 新增 TYPE_USE/TYPE_PARAMETER 支援類型註解。
- 框架開發核心:運行時反射(Spring、JPA)或 編譯時 APT(Lombok、MapStruct、QueryDSL)。
- 重複註解靠
@Repeatable + 容器註解,讀取用 getAnnotationsByType()。
- 類型註解 + Checker Framework = **編譯期發現空指針、SQL注入、正則不匹配** 等邏輯錯誤,極大提升代碼質量。