註解是 Java 元編程的基石,廣泛應用於框架開發、代碼生成、靜態分析。本文系統講解註解核心概念、自定義註解流程、運行時反射處理與編譯時註解處理器(APT)原理,並以 ButterKnife 為例剖析成熟框架的實現機制。
註解本質是繼承 java.lang.annotation.Annotation 的特殊接口,編譯後存儲於 class 文件,運行期通過反射讀取。
| 元註解 | 作用 | 關鍵屬性值 |
|---|---|---|
@Target |
限定註解作用目標 | ElementType.TYPE/FIELD/METHOD/PARAMETER/... |
@Retention |
生命週期策略 | SOURCE(源碼)、CLASS(默認,字節碼)、RUNTIME(運行期,反射可見) |
@Documented |
納入 Javadoc | 標記註解 |
@Inherited |
子類繼承父類註解 | 僅對 TYPE 有效 |
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) // 運行期反射需要
public @interface FruitName {
String value() default ""; // 單屬性慣用 value
}
public class Apple {
@FruitName("Apple")
String appleName;
public void display() {
System.out.println(appleName);
}
}
public class FruitInfoUtils {
public static void getFruitInfo(Class<?> clazz) {
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(FruitName.class)) {
FruitName ann = field.getAnnotation(FruitName.class);
System.out.println("水果名:" + ann.value());
// 可進一步設置字段值(需可訪問權限)
field.setAccessible(true);
field.set(null, ann.value()); // 靜態字段;實例字段需傳實例
}
}
}
}
局限:反射有性能開銷,且無法在編譯期生成代碼、修改字節碼。
JDK 5 引入 apt,JDK 6 標準化 javax.annotation.processing API。處理器在編譯期運行,讀取註解、生成新 Java 文件,實現「寫時生成、編譯即用」。
AbstractProcessor:繼承實現 process(Set<? extends TypeElement>, RoundEnvironment)RoundEnvironment:提供當前輪次被註解的元素集合Filer:生成源文件/類文件/資源文件Messager:編譯期輸出錯誤/警告/信息ButterKnife 通過 @BindView、@OnClick 等註解消除 findViewById、setOnClickListener 模板代碼。
@Retention(CLASS) // 編譯期處理即可,無需運行期保留
@Target(FIELD)
public @interface BindString {
@StringRes int value(); // 字符串資源 ID
}
@AutoService(Processor.class) // SPI 自動註冊
public final class ButterKnifeProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
// 1. 收集所有被 @BindString 註解的字段
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
// 2. 為每個目標類生成綁定類(如 Activity$$ViewBinder)
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
try {
entry.getValue().brewJava().writeTo(filer); // 生成 .java 文件
} catch (IOException e) { error(entry.getKey(), e.getMessage()); }
}
return true;
}
private void parseResourceString(Element element, ...) {
// 驗證字段類型為 String
// 獲取字段名、註解 value (resId)
// 封裝為 FieldResourceBinding 加入 BindingClass
}
}
// 生成的 Activity$$ViewBinder.java
public final class Activity$$ViewBinder implements ViewBinder<Activity> {
@Override
public void bind(Activity target, View source) {
target.loginErrorMessage = source.getContext().getString(R.string.login_error);
// ... 其他綁定代碼
}
}
// ButterKnife.bind(this) 內部:
// 1. 根據類名反射查找/加載生成的 $$ViewBinder 類
// 2. 實例化並調用 bind(target, source)
public static void bind(Activity target) {
ViewBinder<Activity> binder = (ViewBinder<Activity>)
Class.forName(target.getClass().getName() + "$$ViewBinder").newInstance();
binder.bind(target, target.getWindow().getDecorView());
}
| 維度 | 運行時反射 | 編譯時 APT |
|---|---|---|
| 性能 | 運行期反射開銷 | 零運行期開銷,編譯期一次性生成 |
| 功能 | 讀取註解、動態代理、屬性設置 | 生成代碼、資源文件、修改字節碼(配合 ASM) |
| 調試難度 | 較低 | 需查看生成代碼,稍高 |
| 典型框架 | Spring、Hibernate、Retrofit | ButterKnife、Dagger、Room、EventBus 3、MapStruct |
RUNTIME,純編譯期代碼生成用 CLASS 或 SOURCE。META-INF/services/javax.annotation.processing.Processor,免手動配置。// Generated code. Do not modify!。RoundEnvironment 多輪次,支持 IDE 保存即編譯。Messager.printMessage(Kind.ERROR, msg, element) 定位到具體源碼行。Java 註解框架分為運行時反射與編譯時 APT兩大流派。反射靈活簡單,適合框架集成;APT 性能極致、類型安全,適合代碼生成場景。ButterKnife 通過 APT 在編譯期生成綁定類,將 findViewById 等模板代碼轉移到生成代碼中,實現了開發時極簡、運行時零開銷的優雅體驗。掌握註解處理器開發,是構建高質量 Java 框架的核心能力。
