註解是 Java 元編程的基石,廣泛應用於框架開發、代碼生成、編譯時檢查、運行時反射。本文系統講解註解分類、語法規則、元註解作用、自定義註解定義、反射解析、編譯時處理器(APT)六大核心知識點,並提供生產級代碼範例。
| 分類 | 保留策略 | 典型代表 | 應用場景 |
|---|---|---|---|
| 源碼註解 | @Retention(SOURCE) | @Override、@SuppressWarnings | 編譯器檢查、IDE 提示,編譯後丟棄 |
| 編譯時註解 | @Retention(CLASS) | Lombok @Data、MapStruct @Mapper | 字節碼操作、代碼生成,不進入 JVM |
| 運行時註解 | @Retention(RUNTIME) | Spring @Autowired、JPA @Entity | 反射掃描、依賴注入、動態代理 |
@Target({ElementType.METHOD, ElementType.TYPE}) // 作用目標
@Retention(RetentionPolicy.RUNTIME) // 保留策略
@Inherited // 允許繼承
@Documented // 納入 Javadoc
public @interface Description {
String desc(); // 必填成員
String author(); // 必填成員
int age() default 18; // 可選成員 + 默認值
// 成員類型限制:基本類型、String、Class、enum、Annotation、以上類型的一維數組
}
@interface 關鍵字定義default 指定默認值value(),使用時可省略「value =」@Serializable)public enum ElementType {
TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR,
LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE,
TYPE_PARAMETER, TYPE_USE // Java 8 新增,支援類型註解
}
SOURCE:僅源碼保留,編譯器直接丟棄(如 @Override)CLASS:編譯進 class 文件,但 JVM 不加載(默認值,如 Lombok)RUNTIME:JVM 加載,可通過反射讀取(如 Spring 註解)@Inherited,子類**未顯式覆蓋**時可通過反射獲取父類註解@Deprecated、@FunctionalInterface)public class AnnotationParser {
public static void parse(Class> clazz) {
// 1. 類級註解
if (clazz.isAnnotationPresent(Description.class)) {
Description desc = clazz.getAnnotation(Description.class);
System.out.printf("類 %s: %s by %s%n", clazz.getSimpleName(), desc.desc(), desc.author());
}
// 2. 方法級註解
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Description.class)) {
Description desc = method.getAnnotation(Description.class);
System.out.printf(" 方法 %s: %s (age=%d)%n", method.getName(), desc.desc(), desc.age());
}
}
}
}
// 使用示例
@Description(desc = "用戶服務", author = "ZhangSan")
class UserService {
@Description(desc = "查詢用戶", author = "LiSi", age = 25)
public User getUser(Long id) { return null; }
}
實現 javax.annotation.processing.Processor,在編譯期掃描註解生成代碼/配置/文檔。
META-INF/services/javax.annotation.processing.Processor 文件,內容為處理器全限定名AbstractProcessor,重寫 process(Set extends TypeElement>, RoundEnvironment)ProcessingEnvironment 提供的 Filer 生成源文件/資源文件@SupportedAnnotationTypes("com.example.Description")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class DescriptionProcessor extends AbstractProcessor {
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(Description.class)) {
Description desc = element.getAnnotation(Description.class);
// 此處可生成代碼、配置文件、報錯警告
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
"處理註解: " + element + " -> " + desc.desc());
}
return true; // 聲明已處理,不再傳遞給其他處理器
}
}
| 場景 | 推薦策略 |
|---|---|
| 框架開發(DI、AOP、ORM) | @Retention(RUNTIME) + 反射/字節碼增強 |
| 代碼生成、編譯時檢查 | @Retention(CLASS) + APT (如 Lombok、MapStruct) |
| 僅 IDE/編譯器提示 | @Retention(SOURCE) (如 @Override、@Nullable) |
| 公開 API 契約 | 添加 @Documented,編寫詳細 Javadoc |
| 註解成員設計 | 優先使用枚舉限制取值範圍,避免魔法字符串 |
Map, Annotation> 避免重複 getAnnotation()getDeclaredAnnotations() 批量獲取,而非多次 isAnnotationPresent()reflect-config.json「註解是元數據的載體,而非邏輯的容器。好的註解設計:語義清晰、約束嚴格、處理高效、文檔完善。濫用註解會導致『註解驅動開發』反模式,業務邏輯散落在註解屬性中,難以調試與重構。」