首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 自定義註解與反射實戰:從元註解到運行時解析

     編輯:Java 綜合教學

前言:註解驅動開發的基石

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;                // 排序權重
}

註解屬性規則

  • 只有「方法」,無字段;返回類型限於:基本類型、String、Class、enum、註解類型、上述類型的一維數組。
  • 默認值用 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());
        }
    }
}

輸出結果(按 order 排序)

字段/方法: id         類型: int        註解名: 序列號    描述: 
字段/方法: age        類型: int        註解名: 年齡      描述: 
字段/方法: name       類型: String     註解名: 姓名      描述: 
字段/方法: desc       類型: String     註解名:          描述: 描述

四、內置註解機制剖析

@Override

  • @Target(METHOD) @Retention(SOURCE):編譯期檢查方法確實重寫父類方法,否則報錯。
  • 屬於 Marker Annotation(標記型),無屬性,名稱本身攜帶語義。

@Deprecated

  • @Documented @Retention(RUNTIME):標記不推薦使用,編譯器發出警告,運行時可反射檢測。

@SuppressWarnings

  • @Target({TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE}) @Retention(SOURCE):壓制編譯器警告,常用 unchecked、rawtypes、deprecation。
  • 作用域遵循就近原則:方法級 > 類級。

五、AnnotatedElement 核心反射 API

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
    }
}

六、進階主題與最佳實踐

1. 註解處理器(APT)編譯期生成代碼

  • 實現 javax.annotation.processing.Processor,配置 META-INF/services/javax.annotation.processing.Processor。
  • 典型應用:ButterKnife、EventBus、Dagger、Room、Lombok(非標準 APT)。
  • 優勢:零運行時開銷,編譯期報錯,生成 .class 文件。

2. 註解繼承陷阱

  • @Inherited 僅對 @Target(TYPE) 的類級註解生效。
  • 方法/字段註解**不會**被繼承,子類覆寫方法需重新標註。
  • 接口方法註解不被實現類繼承(Java 8+ 默認方法除外)。

3. 參數名編譯保留(Java 8+)

  • 編譯加參數 -parameters,運行時 method.getParameters()[i].getName() 獲取真實參數名。
  • Spring MVC @RequestParam、MyBatis @Param 依賴此機制。

4. 重複註解(Java 8+)


@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() {}

5. 類型註解(Java 8+)

  • @Target(ElementType.TYPE_USE) 可標註泛型參數、類型轉換、instanceof、new 表達式等。
  • 配合 Checker Framework 實現編譯期類型系統增強(非空、正則、單位檢查)。

七、總結

自定義註解 + 反射解析是 Java 元編程的基石。關鍵要點:

  1. 生命週期選 RUNTIME 才能反射讀取;SOURCE 適合編譯期工具,CLASS 適合字節碼操作。
  2. Target 精準限定 使用範圍,避免濫用。
  3. 反射性能:獲取註解涉及類加載、對象創建,高頻熱點路徑建議緩存 Annotation 對象或改用編譯期 APT。
  4. 泛型擦除配合 ParameterizedType 實現「基類泛型參數化 → 子類指定實體 → 基類反射掃描」模式,廣泛用於通用 DAO、導出、驗證框架。
  5. 註解設計原則:職責單一、屬性語義清晰、默認值合理、文檔完善(@Documented)。

掌握這套機制,即可理解主流框架核心原理,並能構建屬於業務領域的聲明式基礎設施。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved