Java 註解(Annotation)是附加在代碼中的元數據,不直接影響業務邏輯,但為編譯期檢查、代碼生成、運行時框架提供豐富語義信息。Spring、Hibernate、JUnit、MyBatis 等主流框架核心均建立在註解解析之上。本文系統講解自定義註解定義、元註解配置、反射解析實戰及最佳實踐。
定義註解行為的四大基石:
| 元註解 | 作用 | 關鍵屬性/取值 |
|---|---|---|
| @Retention | 生命週期策略 | SOURCE(源碼)、CLASS(默認,字節碼)、RUNTIME(運行時可反射) |
| @Target | 作用目標範圍 | ElementType:TYPE、FIELD、METHOD、PARAMETER、CONSTRUCTOR、LOCAL_VARIABLE、ANNOTATION_TYPE、PACKAGE、MODULE(Java 9+) |
| @Documented | 納入 Javadoc | 標記後 javadoc -public 會包含該註解信息 |
| @Inherited | 允許子類繼承 | 僅對類級註解有效,查找順序:當前類→父類→Object |
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
@Documented
public @interface FieldMeta {
boolean id() default false; // 是否主鍵
String name() default ""; // 顯示名稱
boolean editable() default true; // 是否可編輯
boolean summary() default true; // 列表顯示
String description() default ""; // 描述
int order() default 0; // 排序權重
}
default 指定;無默認值則使用時必須賦值。value 時,使用可省略屬性名:@MyAnno("val")。@MyAnno(value={"a","b"})。
public class Anno {
@FieldMeta(id = true, name = "序列號", order = 1)
private int id;
@FieldMeta(name = "姓名", order = 3)
private String name;
@FieldMeta(name = "年齡", order = 2)
private int age;
@FieldMeta(description = "描述", order = 4)
public String desc() {
return "Java 反射獲取 Annotation 測試";
}
// getter/setter 省略
}
public class Parent<T> {
private Class<T> entityClass;
@SuppressWarnings("unchecked")
public List<SortableField> init() {
List<SortableField> list = new ArrayList<>();
// 1. 獲取實體類型:通過泛型父類參數化類型拿到 T 的實際 Class
this.entityClass = (Class<T>) ((ParameterizedType)
getClass().getGenericSuperclass()).getActualTypeArguments()[0];
if (entityClass == null) return list;
// 2. 掃描字段
for (Field f : entityClass.getDeclaredFields()) {
FieldMeta meta = f.getAnnotation(FieldMeta.class);
if (meta != null) {
list.add(new SortableField(meta, f));
}
}
// 3. 掃描公共方法(含繼承)
for (Method m : entityClass.getMethods()) {
FieldMeta meta = m.getAnnotation(FieldMeta.class);
if (meta != null) {
list.add(new SortableField(meta, m.getName(), m.getReturnType()));
}
}
// 4. 按 order 排序
list.sort(Comparator.comparingInt(sf -> sf.getMeta().order()));
return list;
}
}
public class SortableField {
private FieldMeta meta;
private Field field; // 字段模式
private String name; // 統一名稱
private Class<?> type; // 統一類型
public SortableField(FieldMeta meta, Field field) {
this.meta = meta;
this.field = field;
this.name = field.getName();
this.type = field.getType();
}
public SortableField(FieldMeta meta, String name, Class<?> type) {
this.meta = meta;
this.name = name;
this.type = type;
}
// getter/setter 省略
}
public class Child extends Parent<Anno> { }
public class TestAnnotation {
public static void main(String[] args) {
Parent<Anno> p = new Child();
List<SortableField> list = p.init();
for (SortableField sf : list) {
System.out.printf("字段/方法: %-10s 類型: %-10s 註解名: %-8s 描述: %s%n",
sf.getName(), sf.getType().getSimpleName(),
sf.getMeta().name(), sf.getMeta().description());
}
}
}
字段/方法: id 類型: int 註解名: 序列號 描述: 字段/方法: age 類型: int 註解名: 年齡 描述: 字段/方法: name 類型: String 註解名: 姓名 描述: 字段/方法: desc 類型: String 註解名: 描述: 描述
@Target(METHOD) @Retention(SOURCE):編譯期檢查方法確實重寫父類方法,否則報錯。@Documented @Retention(RUNTIME):標記不推薦使用,編譯器發出警告,運行時可反射檢測。@Target({TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE}) @Retention(SOURCE):壓制編譯器警告,常用 unchecked、rawtypes、deprecation。Class、Field、Method、Constructor、Package 均實現此接口:
| 方法 | 返回 | 說明 |
|---|---|---|
| isAnnotationPresent(Class<? extends Annotation>) | boolean | 快速判斷是否存在(適合 Marker Annotation) |
| getAnnotation(Class<T>) | T | 獲取指定註解實例(單個) |
| getAnnotations() | Annotation[] | 獲取所有註解(含繼承) |
| getDeclaredAnnotations() | Annotation[] | 僅獲取直接聲明的註解(不含繼承) |
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String[] value() default "abc";
}
public class Demo {
@MyAnnotation(value = {"a", "b"})
@Deprecated
public void execute() { System.out.println("method"); }
}
public static void main(String[] args) throws Exception {
Method m = Demo.class.getMethod("execute");
if (m.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation ann = m.getAnnotation(MyAnnotation.class);
m.invoke(new Demo()); // 執行方法
System.out.println(ann.value()[0]); // 輸出 a
}
for (Annotation a : m.getAnnotations()) {
System.out.println(a); // 輸出 @MyAnnotation、@Deprecated
}
}
javax.annotation.processing.Processor,配置 META-INF/services/javax.annotation.processing.Processor。@Inherited 僅對 @Target(TYPE) 的類級註解生效。-parameters,運行時 method.getParameters()[i].getName() 獲取真實參數名。
@Repeatable(Container.class)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Schedule { String cron(); }
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Container { Schedule[] value(); }
// 使用
@Schedule("0 0 12 * * ?")
@Schedule("0 0 18 * * ?")
public void runTwiceDaily() {}
@Target(ElementType.TYPE_USE) 可標註泛型參數、類型轉換、instanceof、new 表達式等。自定義註解 + 反射解析是 Java 元編程的基石。關鍵要點:
Annotation 對象或改用編譯期 APT。掌握這套機制,即可理解主流框架核心原理,並能構建屬於業務領域的聲明式基礎設施。