什麼是註解?

1、概念

Java 註解是在 JDK5 時引入的新特性,註解(也被稱爲元數據)爲咱們在代碼中添加信息提供了一種形式化的方法,使咱們能夠在稍後某個時刻很是方便地使用這些數據。註解類型定義指定了一種新的類型,一種特殊的接口類型。 在關鍵詞 interface 前加 @ 符號也就是用 @interface 來區分註解的定義和普通的接口聲明。目前大部分框架(如 Spring Boot 等)都經過使用註解簡化了代碼並提升的編碼效率。html

2、做用

  • 提供信息給編譯器: 編譯器能夠利用註解來探測錯誤和警告信息,如 @Override、@Deprecated。
  • 編譯階段時的處理: 軟件工具能夠用來利用註解信息來生成代碼、Html 文檔或者作其它相應處理,如 @Param、@Return、@See、@Author 用於生成 Javadoc 文檔。
  • 運行時的處理: 某些註解能夠在程序運行的時候接受代碼的提取,值得注意的是,註解不是代碼自己的一部分。如Spring 2.5 開始註解配置,減小了配置。

3、定義

2.1 註解的本質

全部的註解本質上都是繼承自 Annotation 接口。可是,手動定義一個接口繼承 Annotation 接口無效的,須要經過 @interface 聲明註解,Annotation 接口自己也不定義註解類型,只是一個普通的接口。java

public interface Annotation {
    
    boolean equals(Object obj);
    
    int hashCode();
    
    String toString();
    
    /**
     *獲取註解類型 
     */
    Class<? extends Annotation> annotationType();
}

來對比下 @interface 定義註解和繼承 Annotation 接口git

public @interface TestAnnotation1 {
}

public interface TestAnnotation2 extends Annotation  {
}

經過使用 javap 指令對比兩個文件的字節碼,發現經過 @interface 定義註解,本質上就是繼承 Annotation 接口。程序員

// javap -c TestAnnotation1.class
Compiled from "TestAnnotation1.java"                                                                 
public interface com.hncboy.corejava.annotation.TestAnnotation1 extends java.lang.annotation.Annotation {}

// javap -c TestAnnotation2.class
Compiled from "TestAnnotation2.java"                                                                 
public interface com.hncboy.corejava.annotation.TestAnnotation2 extends java.lang.annotation.Annotation {}

雖然本質上都是繼承 Annotation 接口,但即便接口能夠實現多繼承,註解的定義仍然沒法使用繼承關鍵字來實現。github

經過 @interface 定義註解後,該註解也不能繼承其餘的註解或接口,註解是不支持繼承的,以下代碼就會報錯。正則表達式

public @interface TestAnnotation1 {
}
/** 錯誤的定義,註解不能繼承註解 */
@interface TestAnnotation2 extends TestAnnotation1 {
}
/** 錯誤的定義,註解不能繼承接口 */
@interface TestAnnotation3 extends Annotation {
}

雖然註解不支持繼承其餘註解或接口,但可使用組合註解的方式來解決這個問題。如 @SpringBootApplication 就採用了組合註解的方式。spring

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    
}

2.2 註解的架構

image

註解的基本架構如圖所示,先簡單瞭解下該架構,後面會詳細講解。編程

該架構的左半部分爲基本註解的組成,一個基本的註解包含了 @interface 以及 ElementType 和 RententionPolicy 這兩個枚舉類。數組

  • Annotation 和 ElementType 是一對多的關係
  • Annotation 和 RetentionPolicy 是一對一的關係

該架構的右半部分爲 JDK 部份內置的標準註解及元註解。架構

  • 標準註解:@Override、@Deprecated 等
  • 元註解:@Documented、@Retention、@Target、@Inherited 等

2.3 註解的屬性

註解的屬性也稱爲成員變量,註解只有成員變量,沒有方法。註解的成員變量在註解的定義中以「無形參的方法」形式來聲明,其方法名定義了該成員變量的名字,其返回值定義了該成員變量的類型。

註解內的可以使用的數據類型是有限制的,類型以下:

  • 全部的基本類型(int,float,boolean 等)
  • String
  • Class
  • enum(@Retention 中屬性的類型爲枚舉)
  • Annotation
  • 以上類型的數組(@Target 中屬性類型爲枚舉類型的數組)

編譯器對屬性的默認值也有約束。首先,屬性不能有不肯定的的值。也就是說,屬性要麼具備默認值,要麼在使用註解時提供屬性的值。對於非基本類型的屬性,不管是在源代碼中聲明時,或是在註解接口中定義默認值時,都不能使用 null 爲其值。所以,爲了繞開這個約束,咱們須要本身定義一些特殊的值,例如空字符串或負數,來表示某個屬性不存在。

經過一個案例來演示下註解可以使用的數據類型及默認值。

@interface Reference {
    boolean contain() default false;
}

enum Week {
    Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
}

public @interface TestAnnotation {

    /**
     * int 基本數據類型
     * @return
     */
    int type() default -1;

    /**
     * boolean 基本數據類型
     * @return
     */
    boolean status() default false;

    /**
     * String 類型
     * @return
     */
    String name() default "";

    /**
     * Class 類型
     * @return
     */
    Class<?> loadClass() default String.class;

    /**
     * 枚舉類型
     * @return
     */
    Week today() default Week.Sunday;

    /**
     * 註解類型
     * @return
     */
    Reference reference() default @Reference(contain = true);

    /**
     * 枚舉數組類型
     * @return
     */
    Week[] value();
}

4、組成

咱們已經瞭解了註解的架構,先來定義一個簡單的註解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}

3.1 ElementType

ElementType 枚舉類型的常量爲 Java 程序中可能出現註解的聲明位置提供了簡單的分類。這些常量用於 @Target 註解中。@Target 用於描述註解適用的範圍,即註解能修飾的對象範圍,經過 ElementType 的枚舉常量表示。

先來看下 ElementType 該枚舉類的代碼。

public enum ElementType {
    
    /**
     * 用於描述類、接口(包括註解類型)、枚舉的定義
     */
    TYPE,

    /**
     * 用於描述成員變量、對象、屬性(包括枚舉常量)
     */
    FIELD,

    /**
     * 用戶描述方法
     */
    METHOD,

    /**
     * 用於描述參數
     */
    PARAMETER,

    /**
     * 用於描述構造器
     */
    CONSTRUCTOR,

    /**
     * 用於描述局部變量
     */
    LOCAL_VARIABLE,

    /**
     * 用於描述註解的(元註解)
     */
    ANNOTATION_TYPE,

    /**
     * 用於描述包
     */
    PACKAGE,

    /*
     * 表示該註解能寫在類型變量的聲明語句中
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * 表示該註解能寫在使用類型的任何語句中(聲明語句、泛型和強制轉換語句中的類型)
     * @since 1.8
     */
    TYPE_USE
}

由於 Annotation 和 ElementType 是一對多的關係,因此 @Target 中能夠存放數組,表示多個範圍,默認全部範圍。

JDK8 以前,註解只能用於聲明的地方,JDK8 中添加了 TYPE_PARAMETER 和 TYPE_USE 類型註解,能夠應用於全部地方:泛型、父類、接口,異常、局部變量等。舉個例子,定義一個 @AnyWhere 註解,Boy 接口和 Test 類。

@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnyWhere {
}

public interface Boy {
}

public class Test<@AnyWhere T> extends @AnyWhere Object implements @AnyWhere Boy {

    private @AnyWhere T test1(@AnyWhere T t) throws @AnyWhere Exception {
        return t;
    }
    
    private void test2() {
        Test<Integer> test = new @AnyWhere Test<>();
        @AnyWhere List<@AnyWhere Integer> list = new ArrayList<>();
    }
}

3.2 RetentionPolicy

RetentionPolicy 枚舉類型的常量用於保留註解的各類策略,即該註解的有效期。它們與 @Retention 註解類型一塊兒使用,以指定保留註解的時間。RetentionPolicy 枚舉的代碼以下。

public enum RetentionPolicy {
    /**
     * 表示該註解只存在於源碼階段,
     */
    SOURCE,

    /**
     * 表示該註解存在於源碼階段和編譯後的字節碼文件裏
     */
    CLASS,

    /**
     * 表示該註解存在於源碼階段、編譯後的字節碼文件和運行時期,且註解的內容將會被 JVM 解釋執行
     * 該範圍的註解可經過反射獲取到
     */
    RUNTIME
}

Annotation 和 RetentionPolicy 是一對一的關係,即每一個註解只能有一種保留策略。

這三個枚舉值是有等級關係的,SOURCE < CLASS < RUNTIME,即 RUNTIME 的有效範圍是最大的,其次的是 CLASS,最小的範圍是 SOURCE,默認的保留範圍爲 CLASS。

  • RUNTIME 範圍使用於在運行期間經過反射的方式去獲取註解。
  • CLASS 適用於編譯時進行一些預處理操做。
  • SOURCE 適用於一些檢查性的工做,或者生成一些輔助的代碼,如 @Override 檢查重寫的方法,Lombok 中的 @Date、@Getter、@Setter 註解。

3.3 註解與反射

經過前面咱們瞭解到,註解本質上繼承 Annotation 接口,也就是說,Annotation 接口是全部註解的父接口。@Retention 的保留策略爲 RetentionPolicy.RUNTIME 的狀況下,咱們能夠經過反射獲取註解的相關信息。Java 在 java.lang.reflect 包下也提供了對註解支持的接口。

image

主要來了解下 AnnotationElement 這個接口,其餘接口都爲該接口的子接口。該接口的對象表明 JVM 運行期間使用註解的類型(Class,Method,Field 等)。該包下的 Constructor 類、Method 類、Package 類和 Class 類等都實現了該接口。簡單瞭解下該接口的部分函數。

public interface AnnotatedElement {
    /**
     * default 方法是 Java8 新增的
     * 若是指定類型的註解存在該類型上,則返回 true,不然返回 false。此方法的主要目的是方便訪問一些已知的註解
     *
     * @param annotationClass 該泛型參數表示全部繼承了Annotation 接口的接口,也就是註解
     * @return 返回該類型上是否有指定的註解
     */
    default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return getAnnotation(annotationClass) != null;
    }
    
    /**
     * 根據註解的 Class 查詢註解
     */
    <T extends Annotation> T getAnnotation(Class<T> annotationClass);
    
    /**
     * 返回該類型上的全部註解,包含繼承的
     */
    Annotation[] getAnnotations();
    
    /**
     * 返回該類型上的全部註解,不包含繼承的
     */
    Annotation[] getDeclaredAnnotations();
}

咱們使用代碼來測試下反射獲取註解。定義兩個註解,一個保留策略爲 RetentionPolicy.RUNTIME,另外一個爲 RetentionPolicy.CLASS。建立 TestAnnotation 類測試註解,該類上使用了這兩個註解。

@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation1 {
    String status() default "hncboy";
}

@Retention(RetentionPolicy.CLASS)
public @interface TestAnnotation2 {
    String value() default "hncboy";
}

@TestAnnotation1(status = "hncboy2")
@TestAnnotation2("hncboy2")
public class TestAnnotation {

    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> clazz = Class.forName("com.hncboy.corejava.annotation.TestAnnotation");
        // 獲取該類的全部註解
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation.annotationType());
            System.out.println(annotation.toString());
        }
    }
}

輸出結果以下,可見 TestAnnotation2 註解沒有輸出,由於 TestAnnotation2 註解類型是 RetentionPolicy.CLASS 的,因此用反射方法獲取不到。這裏還涉及到了註解的一個快捷方法,就是當註解裏的屬性名字定義爲 value 時,能夠在使用該註解時不指定屬性名,上面的 @Target 註解和 @Retention 註解都屬於這種狀況,不過當註解裏有多個屬性時,那就必須指定屬性名了。

interface com.hncboy.corejava.annotation.TestAnnotation1
@com.hncboy.corejava.annotation.TestAnnotation1()(status=hncboy2)

5、元註解

元註解即註解的註解且只能做用於註解上的註解,也就是說元註解負責其餘註解的註解,並且只能用在註解上面。

JDK8 之前內置的元註解有 @Documented、@Retention、@Target、@Inherited 這四個,JDK 8 引入了 @Repeatable, 前面已經瞭解過了 @Target 和 @Retention,下面作一些簡單的補充。

元註解的 @Target 都爲 ElementType.ANNOTATION_TYPE,由於元註解只能應用於註解的註解。元註解在定義該註解的同時也能夠直接使用該註解。

5.1 @Target

該註解用於定義註解能使用的範圍,取值爲 ElementType 枚舉。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * 返回能夠應用註解類型的各類範圍的枚舉數組
     * 名字爲 value 時能夠省略屬性名
     * @return
     */
    ElementType[] value();
}

使用方式:

@Target(ElementType.METHOD)
@Target(value = ElementType.METHOD)
@Target({ElementType.METHOD, ElementType.TYPE})
@Target(value = {ElementType.METHOD, ElementType.TYPE})

5.2 @Retention

該註解定義註解的保留策略或者說定義註解的有效期,取值範圍爲 RetationPolicy 枚舉。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * 返回保留策略
     * @return
     */
    RetentionPolicy value();
}

使用方式:

@Retention(RetentionPolicy.RUNTIME)
@Retention(value = RetentionPolicy.RUNTIME)

5.3 @Documented

該註解的使用表示是否包含在生成的 javadoc 文檔中。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

舉個例子,定義一個 @TestAnnotation 註解和 Test 類。

@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {

}

@TestAnnotation
public class Test {

}

經過 javadoc -d doc *.java 命令將該目錄下的這兩個類生成文檔並放在 doc 目錄下。生成的文件以下,點擊 index.html。

image

看到如圖所示的樣子,Test 類中包含 @TestAnnotation。

image

咱們再把 @TestAnnotation 註解上的 @Documenet 註解註釋掉再來生成下文檔。此時發現 Test 類中沒有 @TestAnnotation 註解了。

image

5.4 @Inherited

該註解表示註解是否具備繼承的特性。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

舉個例子來測試下。新建 TestAnnotation 註解,Father 類,Son 類,Father 類使用了該註解,Son 類繼承 Father 類。

@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface TestAnnotation {
}

@TestAnnotation
public class Father {
}

public class Son extends Father {
}

新建一個測試類,測試 Father 和 Son 這兩個類是否包這兩個註解。

public class Test {

    public static void main(String[] args) {
        System.out.println(Father.class.isAnnotationPresent(TestAnnotation.class));
        System.out.println(Son.class.isAnnotationPresent(TestAnnotation.class));
    }
}

輸出爲 true true,當把 @TestAnnotation 註解上的 @Inherited 註解註釋掉時,輸出 true false,如此可見該註解的做用。

5.5 @Repeatable

JDK8 之前是不支持重複註解的,同一個地方只能使用同一個註解一次。 該註解從 JDK8 引入,該註解類型用於表示其聲明註解的註解類型爲可重複時。 value() 的值表示可重複註解的類型,包含註解類型。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * 指可重複註解的類型,包含註解類型
     * @return
     */
    Class<? extends Annotation> value();
}

舉個例子,定義 @Activity 和 @Activities 註解,定義 Hncboy 類測試重複註解。@Activity 註解被 @Repeatable(Activities.class) 註解,@Activities 至關於一個容器註解,屬性爲 Activity 類型的數組,經過這樣的方式,使得 @Activity 註解能夠被重複使用。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Activities {
    Activity[] value();
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Activities.class)
public @interface Activity {
    String value();
}

@Activity("打代碼")
@Activity("吃飯")
@Activity("睡覺")
public class Hncboy {
}

@Activities({@Activity("打代碼"), @Activity("吃飯"), @Activity("睡覺")})
public class Hncboy {
}

6、標準註解

JDK 內置的註解有 @Deprecated、@Override、@SuppressWarnnings、@SafeVarargs(JDK 7 引入)、@FunctionalInterface(JDK 引入)等。接下來介紹下 3 中經常使用的內置註解。

6.1 @Deprecated

註解爲 @Deprecated 的類型是不鼓勵程序員使用的元素,一般是由於這樣作很危險,或者是由於存在更好的替代方法。當在不推薦使用的代碼中使用或覆蓋不推薦使用的程序元素時,編譯器會發出警告。該註解能夠用來修飾構造器、字段、局部變量、方法等類型。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

舉個例子,使用 @Deprecated 修飾的元素是不推薦使用的,編譯器會幫咱們將這些類和方法用刪除線標記。直接聲明在包上會報 Package annotations should be in file package-info.java 錯誤。

@Deprecated
public class TestDeprecated {

    @Deprecated
    String s = "hncboy";

    @Deprecated
    public void test() {
    }
}

image

6.2 @Override

@Override 註解咱們常常用到,提示子類須要重寫父類的方法。方法重寫或實現了在父類中聲明的方法時須要加上該註解,該註解用於編譯器檢查重寫的操做是否正確,保留策略爲 RetentionPolicy.SOURCE。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

6.3 @SuppressWarnings

用來關閉編譯器生成警告信息,能夠用來修飾類、方法、成員變量等,在使用該註解時,應採用就近原則,如方法產生警告是,應該針對方法聲明該註解,而不是對類聲明,有利於發現該類的其餘警告信息。

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    /**
     * 帶有註解的元素中的編譯器將禁止的警告集。
     * 使用 unchecked 忽略沒法識別的警告
     */
    String[] value();
}

舉個例子,rawtypes 用於使用泛型時忽略沒有指定相應的類型,unused 用於沒有使用過的代碼。

public class Test {

    @SuppressWarnings({"rawtypes", "unused"})
    private List test() {
        return new ArrayList();
    }
}

7、自定義註解

自定義註解實現 Spring IOC Bean 實例建立,自定義簡單的註解: @Component、@Bean 和 @ComponentScan。

經過什麼是反射?這篇文章咱們已經學習到經過反射實現 Spring IOC Bean 實例的三種建立方式,不清楚的能夠去看下那篇文章。

7.1 新建 @MyComponent、@MyBean、 @MyComponentScan

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyBean {

    String value() default "";
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {

    String value() default "";
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponentScan {

    String value() default "";
}

7.2 新建 A、B、C 三個類

@MyComponent("a")
public class A {

    public A() {
        System.out.println("調用 A 的無參構造器");
    }

    @MyBean("b")
    public static B createBInstance() {
        System.out.println("調用 A 的靜態方法 createBInstance");
        return new B();
    }

    @MyBean("c")
    public C createCInstance() {
        System.out.println("調用 A 的實例方法 createCInstance");
        return new C();
    }
}

class B {}
class C {}

7.3 新建 IOCContainer 類

/**
 * 定義 map 存放 bean
 */
public class IOCContainer {

    private static HashMap<String, Object> container = new HashMap<>();

    public static void putBean(String id, Object object) {
        container.put(id, object);
    }

    public static Object getBean(String id) {
        return container.get(id);
    }
}

7.4 新建 Test 類

  • 先獲取 @MyComponentScan 註解中的包名
  • 而後掃描該包下全部類的全限定名
  • 遍歷類名,判斷改類是否實現 @MyComponent 註解
  • 遍歷方法,判斷該方法是否實現 @MyBean 註解

大體過程是這樣,具體的能夠見代碼的註釋。

@MyComponentScan("com.hncboy.corejava.annotation.spring")
public class Test {

    public static void main(String[] args) throws Exception {
        Test test = new Test();
        // 獲取 MyComponentScan 註解中的包名
        String scanPackage = test.getScanPackage();

        HashSet<String> classPathSet = new HashSet<>();
        // 掃描包下的全部類並將類的全限定名放進 classPathSet
        test.doScanPackage(classPathSet, scanPackage);

        // 遍歷掃描包下的全部類
        for (String className : classPathSet) {
            // 經過類的全限定名獲取 Class
            Class<?> clazz = Class.forName(className);
            // 判斷該類是否實現了 MyComponent 註解
            if (clazz.isAnnotationPresent(MyComponent.class)) {
                // 方式1:經過構造器實例化
                IOCContainer.putBean(className, clazz.newInstance());
            }

            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                // 判斷方法是否有 MyBean 註解
                if (method.isAnnotationPresent(MyBean.class)) {
                    // 獲取 bean 值
                    String beanName = method.getAnnotation(MyBean.class).value();
                    // 判斷該方法是不是靜態方法或實例方法
                    if (Modifier.isStatic(method.getModifiers())) {
                        // 方式2:經過靜態工廠實例化
                        IOCContainer.putBean(beanName, method.invoke(null));
                    } else {
                        // 方式3:經過實例工廠實例化
                        // 首先獲取該類的實例對象,再調用實例方法進行實例化
                        IOCContainer.putBean(beanName, method.invoke(IOCContainer.getBean(className)));
                    }
                }
            }
        }
    }

    /**
     * 獲取 MyComponentScan 註解中的包名
     *
     * @return
     */
    private String getScanPackage() {
        Class<?> clazz = this.getClass();
        if (!clazz.isAnnotationPresent(MyComponentScan.class)) {
            return "";
        }
        MyComponentScan scanPackage = clazz.getDeclaredAnnotation(MyComponentScan.class);
        return scanPackage.value();
    }

    /**
     * 掃描該包下的類
     *
     * @param classPathSet
     * @param scanPackage
     */
    private void doScanPackage(HashSet<String> classPathSet, String scanPackage) {
        // 經過正則表達式將包名中的 . 替代爲 /,並獲取到該路徑的 class url
        URL url = this.getClass().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        // 獲取該 url 下的全部 File(目錄/文件)
        File classDir = new File(url.getFile());
        // 遍歷全部 File
        for (File file : classDir.listFiles()) {
            // 判斷該 file 若是是目錄的話
            if (file.isDirectory()) {
                // 拼接該目錄的名字並遞歸遍歷該目錄
                doScanPackage(classPathSet, scanPackage + "." + file.getName());
            } else {
                // 若是文件不是以 .class 結尾
                if (!file.getName().endsWith(".class")) {
                    continue;
                }

                // 經過 包名+目錄名+除去.class的類名 拼接該類的全限定名
                String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));
                // 將該類的全限定名放入 classPathSet
                classPathSet.add(clazzName);
            }
        }
    }
}

輸出以下:

調用 A 的無參構造器
調用 A 的靜態方法 createBInstance
調用 A 的實例方法 createCInstance

注:APT——這些處理提取和處理 Annotation 的代碼統稱爲 APT(Annotation Processing Tool)。

Java 編程思想

Java Annotation認知

文章同步到公衆號和Github,有問題的話能夠聯繫做者。

燦爛一輩子 公衆號

相關文章
相關標籤/搜索