Java 註解(Annotation)是 JDK 5 引入的元資料機制,廣泛應用於框架開發、編譯檢查、程式碼生成等場景。本文系統梳理註解基礎、元註解、自定義註解、註解處理器,並深入剖析 ButterKnife 註解框架的編譯時處理原理。
註解是一種標記,像 @Override、@Target 等,它們不直接影響程式碼邏輯,但提供給編譯器、工具或執行期反射使用。註解資訊保留在 .class 檔案中(視保留策略而定),類似 C 語言的巨集但更強大,基於反射機制實現。
元註解是用來修飾其他註解的註解,Java 內建四種:
RetentionPolicy.RUNTIME:執行期可透過反射獲取。RetentionPolicy.CLASS(預設):編譯期保留在 class 檔,執行期不可見。RetentionPolicy.SOURCE:僅原始碼階段存在,編譯時丟棄。定義一個水果名稱註解 @FruitName,作用於欄位,執行期保留:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitName {
String value() default "";
}
使用範例:
public class Apple {
@FruitName("Apple")
String appleName;
public void displayAppleName() {
TraceLog.i(appleName); // 此處 appleName 為 null,因為註解本身不自動賦值
}
}
註解只是標記,需透過註解處理器讀取並賦值。
透過反射掃描類別欄位,讀取註解屬性並設定值:
public class FruitInfoUtils {
public static void getFruitInfo(Class> clazz) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(FruitName.class)) {
FruitName fruitName = field.getAnnotation(FruitName.class);
String name = fruitName.value();
// 此處可進一步將 name 設定回 field(需 field.setAccessible(true))
TraceLog.i(name);
}
}
}
}
ButterKnife 等框架利用編譯時註解處理器(APT),在編譯階段生成輔助類別,避免執行期反射開銷。
@Retention(CLASS)
@Target(FIELD)
public @interface BindString {
@StringRes int value();
}
RoundEnvironment 獲取所有標註 @BindString 的元素。FieldResourceBinding,存入 Map。BindingClass.brewJava() 使用 JavaPoet 生成 ViewBinder 實作類別,寫入 .java 檔案。// 核心生成邏輯片段
JavaFile brewJava() {
TypeSpec.Builder result = TypeSpec.classBuilder(generatedClassName)
.addModifiers(PUBLIC);
// ... 繼承關係、bind 方法、unbinder 類別生成 ...
return JavaFile.builder(packageName, result.build())
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
targetClassMap,最終由 process() 方法觸發程式碼生成。javax.annotation.processing API,支援自定義 Processor。