註解是附加在程式碼上的元資料,不直接影響邏輯,但可被編譯器、工具、執行時反射讀取。四大元註解定義於 java.lang.annotation:
| 元註解 | 用途 | 關鍵屬性 |
|---|---|---|
@Target | 限定註解可修飾的程式元素 | ElementType.TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE |
@Retention | 定義註解保留階段 | SOURCE (源碼), CLASS (預設, 字節碼), RUNTIME (執行時可反射) |
@Documented | 標示註解應包含於 Javadoc | 無 |
@Inherited | 允許子類繼承父類的此註解 (僅限 TYPE) | 無 |
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
int id() default -1;
}
// 使用
public class Apple {
@FruitName(value = "Apple", id = 1)
private String name;
}
public class FruitInfoUtil {
public static void printFruitInfo(Class<?> clazz) {
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(FruitName.class)) {
FruitName annotation = field.getAnnotation(FruitName.class);
System.out.printf("Field: %s, Name: %s, ID: %d%n",
field.getName(), annotation.value(), annotation.id());
}
}
}
}
核心優勢:編譯期生成代碼,避免執行時反射開銷,實現零樣板代碼 (如 ButterKnife, Dagger, Lombok, Room)。
@SupportedAnnotationTypes("com.example.annotation.BindView")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class BindViewProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
// 1. 收集所有被 @BindView 標註的元素
Map<TypeElement, List<VariableElement>> targetMap = new HashMap<>();
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
if (element.getKind() != ElementKind.FIELD) continue;
VariableElement field = (VariableElement) element;
TypeElement enclosingClass = (TypeElement) field.getEnclosingElement();
targetMap.computeIfAbsent(enclosingClass, k -> new ArrayList<>()).add(field);
}
// 2. 為每個類生成綁定代碼
for (Map.Entry<TypeElement, List<VariableElement>> entry : targetMap.entrySet()) {
generateBindingClass(entry.getKey(), entry.getValue());
}
return true; // 聲稱已處理這些註解
}
private void generateBindingClass(TypeElement classElement, List<VariableElement> fields) {
// 使用 JavaPoet 或字串拼接生成 .java 檔案
// 範例:生成 $$ViewBinder 類,在 bind() 方法中 findViewById 並賦值
try {
JavaFile.builder(packageName, generateTypeSpec(classElement, fields))
.build()
.writeTo(processingEnv.getFiler());
} catch (IOException e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
}
}
}
# 檔案: src/main/resources/META-INF/services/javax.annotation.processing.Processor
com.example.processor.BindViewProcessor
@Retention(CLASS) // 編譯期處理,執行時不保留
@Target(FIELD)
public @interface BindView {
int value(); // 對應 R.id.xxx
}
process() 掃描所有 @BindView 欄位TargetClass$$ViewBinder 類bind(T target, View source) 方法,內含 target.field = source.findViewById(id)ButterKnife.bind(this) 透過反射/緩存調用生成的綁定類| 場景 | 保留策略 | 處理時機 | 典型案例 |
|---|---|---|---|
| 編譯期檢查/生成代碼 | CLASS / SOURCE | 編譯時 (APT) | Lombok, ButterKnife, Dagger, Room, MapStruct |
| 執行時掃描/配置 | RUNTIME | 啟動/請求時 (反射) | Spring @Autowired, JPA @Entity, JAX-RS @Path |
| 文檔生成 | SOURCE + @Documented | Javadoc 階段 | 自定義文檔標籤 |