什么是注解?

一挎袜、概念

Java 注解是在 JDK5 時引入的新特性顽聂,注解(也被稱為元數(shù)據(jù))為我們在代碼中添加信息提供了一種形式化的方法肥惭,使我們可以在稍后某個時刻非常方便地使用這些數(shù)據(jù)盯仪。注解類型定義指定了一種新的類型,一種特殊的接口類型蜜葱。 在關(guān)鍵詞 interface 前加 @ 符號也就是用 @interface 來區(qū)分注解的定義和普通的接口聲明全景。目前大部分框架(如 Spring Boot 等)都通過使用注解簡化了代碼并提高的編碼效率。

二牵囤、作用

  • 提供信息給編譯器: 編譯器可以利用注解來探測錯誤和警告信息爸黄,如 @Override、@Deprecated揭鳞。
  • 編譯階段時的處理: 軟件工具可以用來利用注解信息來生成代碼炕贵、Html 文檔或者做其它相應(yīng)處理,如 @Param野崇、@Return称开、@See、@Author 用于生成 Javadoc 文檔乓梨。
  • 運(yùn)行時的處理: 某些注解可以在程序運(yùn)行的時候接受代碼的提取鳖轰,值得注意的是,注解不是代碼本身的一部分扶镀。如Spring 2.5 開始注解配置蕴侣,減少了配置。

三臭觉、定義

2.1 注解的本質(zhì)

所有的注解本質(zhì)上都是繼承自 Annotation 接口昆雀。但是,手動定義一個接口繼承 Annotation 接口無效的蝠筑,需要通過 @interface 聲明注解狞膘,Annotation 接口本身也不定義注解類型,只是一個普通的接口菱肖。

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

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

public @interface TestAnnotation1 {
}

public interface TestAnnotation2 extends Annotation  {
}

通過使用 javap 指令對比兩個文件的字節(jié)碼客冈,發(fā)現(xiàn)通過 @interface 定義注解,本質(zhì)上就是繼承 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 {}

雖然本質(zhì)上都是繼承 Annotation 接口场仲,但即使接口可以實(shí)現(xiàn)多繼承和悦,注解的定義仍然無法使用繼承關(guān)鍵字來實(shí)現(xiàn)。

通過 @interface 定義注解后渠缕,該注解也不能繼承其他的注解或接口鸽素,注解是不支持繼承的,如下代碼就會報錯亦鳞。

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

雖然注解不支持繼承其他注解或接口燕差,但可以使用組合注解的方式來解決這個問題遭笋。如 @SpringBootApplication 就采用了組合注解的方式。

@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 注解的架構(gòu)

image

注解的基本架構(gòu)如圖所示徒探,先簡單了解下該架構(gòu)瓦呼,后面會詳細(xì)講解。

該架構(gòu)的左半部分為基本注解的組成测暗,一個基本的注解包含了 @interface 以及 ElementType 和 RententionPolicy 這兩個枚舉類央串。

  • Annotation 和 ElementType 是一對多的關(guān)系
  • Annotation 和 RetentionPolicy 是一對一的關(guān)系

該架構(gòu)的右半部分為 JDK 部分內(nèi)置的標(biāo)準(zhǔn)注解及元注解。

  • 標(biāo)準(zhǔn)注解:@Override碗啄、@Deprecated 等

  • 元注解:@Documented质和、@Retention、@Target稚字、@Inherited 等

2.3 注解的屬性

注解的屬性也稱為成員變量饲宿,注解只有成員變量,沒有方法尉共。注解的成員變量在注解的定義中以“無形參的方法”形式來聲明褒傅,其方法名定義了該成員變量的名字,其返回值定義了該成員變量的類型袄友。

注解內(nèi)的可使用的數(shù)據(jù)類型是有限制的殿托,類型如下:

  • 所有的基本類型(int,float剧蚣,boolean 等)
  • String
  • Class
  • enum(@Retention 中屬性的類型為枚舉)
  • Annotation
  • 以上類型的數(shù)組(@Target 中屬性類型為枚舉類型的數(shù)組)

編譯器對屬性的默認(rèn)值也有約束支竹。首先,屬性不能有不確定的的值鸠按。也就是說礼搁,屬性要么具有默認(rèn)值,要么在使用注解時提供屬性的值目尖。對于非基本類型的屬性馒吴,無論是在源代碼中聲明時,或是在注解接口中定義默認(rèn)值時,都不能使用 null 為其值饮戳。因此豪治,為了繞開這個約束,我們需要自己定義一些特殊的值扯罐,例如空字符串或負(fù)數(shù)负拟,來表示某個屬性不存在。

通過一個案例來演示下注解可使用的數(shù)據(jù)類型及默認(rèn)值歹河。

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

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

public @interface TestAnnotation {

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

    /**
     * boolean 基本數(shù)據(jù)類型
     * @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);

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

四掩浙、組成

我們已經(jīng)了解了注解的架構(gòu),先來定義一個簡單的注解秸歧。

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

3.1 ElementType

ElementType 枚舉類型的常量為 Java 程序中可能出現(xiàn)注解的聲明位置提供了簡單的分類厨姚。這些常量用于 @Target 注解中。@Target 用于描述注解適用的范圍寥茫,即注解能修飾的對象范圍遣蚀,通過 ElementType 的枚舉常量表示。

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

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

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

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

    /**
     * 用于描述參數(shù)
     */
    PARAMETER,

    /**
     * 用于描述構(gòu)造器
     */
    CONSTRUCTOR,

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

    /**
     * 用于描述注解的(元注解)
     */
    ANNOTATION_TYPE,

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

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

    /**
     * 表示該注解能寫在使用類型的任何語句中(聲明語句甩牺、泛型和強(qiáng)制轉(zhuǎn)換語句中的類型)
     * @since 1.8
     */
    TYPE_USE
}

因?yàn)?Annotation 和 ElementType 是一對多的關(guān)系蘑志,所以 @Target 中可以存放數(shù)組,表示多個范圍贬派,默認(rèn)所有范圍急但。

JDK8 之前,注解只能用于聲明的地方搞乏,JDK8 中添加了 TYPE_PARAMETER 和 TYPE_USE 類型注解波桩,可以應(yīng)用于所有地方:泛型、父類请敦、接口镐躲,異常、局部變量等侍筛。舉個例子萤皂,定義一個 @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,

    /**
     * 表示該注解存在于源碼階段和編譯后的字節(jié)碼文件里
     */
    CLASS,

    /**
     * 表示該注解存在于源碼階段纷跛、編譯后的字節(jié)碼文件和運(yùn)行時期喻括,且注解的內(nèi)容將會被 JVM 解釋執(zhí)行
     * 該范圍的注解可通過反射獲取到
     */
    RUNTIME
}

Annotation 和 RetentionPolicy 是一對一的關(guān)系,即每個注解只能有一種保留策略贫奠。

這三個枚舉值是有等級關(guān)系的唬血,SOURCE < CLASS < RUNTIME,即 RUNTIME 的有效范圍是最大的唤崭,其次的是 CLASS拷恨,最小的范圍是 SOURCE,默認(rèn)的保留范圍為 CLASS谢肾。

  • RUNTIME 范圍使用于在運(yùn)行期間通過反射的方式去獲取注解腕侄。
  • CLASS 適用于編譯時進(jìn)行一些預(yù)處理操作。
  • SOURCE 適用于一些檢查性的工作芦疏,或者生成一些輔助的代碼冕杠,如 @Override 檢查重寫的方法,Lombok 中的 @Date酸茴、@Getter分预、@Setter 注解。

3.3 注解與反射

通過前面我們了解到薪捍,注解本質(zhì)上繼承 Annotation 接口笼痹,也就是說,Annotation 接口是所有注解的父接口酪穿。@Retention 的保留策略為 RetentionPolicy.RUNTIME 的情況下凳干,我們可以通過反射獲取注解的相關(guān)信息。Java 在 java.lang.reflect 包下也提供了對注解支持的接口被济。

image

主要來了解下 AnnotationElement 這個接口救赐,其他接口都為該接口的子接口。該接口的對象代表 JVM 運(yùn)行期間使用注解的類型(Class溉潭,Method净响,F(xiàn)ield 等)。該包下的 Constructor 類喳瓣、Method 類馋贤、Package 類和 Class 類等都實(shí)現(xiàn)了該接口。簡單了解下該接口的部分函數(shù)畏陕。

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

我們使用代碼來測試下反射獲取注解腰埂。定義兩個注解飒焦,一個保留策略為 RetentionPolicy.RUNTIME,另一個為 RetentionPolicy.CLASS屿笼。創(chuàng)建 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());
        }
    }
}

輸出結(jié)果如下驴一,可見 TestAnnotation2 注解沒有輸出休雌,因?yàn)?TestAnnotation2 注解類型是 RetentionPolicy.CLASS 的,所以用反射方法獲取不到肝断。這里還涉及到了注解的一個快捷方法杈曲,就是當(dāng)注解里的屬性名字定義為 value 時,可以在使用該注解時不指定屬性名胸懈,上面的 @Target 注解和 @Retention 注解都屬于這種情況担扑,不過當(dāng)注解里有多個屬性時议薪,那就必須指定屬性名了。

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

五择同、元注解

元注解即注解的注解且只能作用于注解上的注解第焰,也就是說元注解負(fù)責(zé)其他注解的注解,而且只能用在注解上面腻格。

JDK8 以前內(nèi)置的元注解有 @Documented、@Retention、@Target绞灼、@Inherited 這四個,JDK 8 引入了 @Repeatable呈野, 前面已經(jīng)了解過了 @Target 和 @Retention低矮,下面做一些簡單的補(bǔ)充。

元注解的 @Target 都為 ElementType.ANNOTATION_TYPE被冒,因?yàn)樵⒔庵荒軕?yīng)用于注解的注解军掂。元注解在定義該注解的同時也可以直接使用該注解。

5.1 @Target

該注解用于定義注解能使用的范圍昨悼,取值為 ElementType 枚舉蝗锥。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * 返回可以應(yīng)用注解類型的各種范圍的枚舉數(shù)組
     * 名字為 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 目錄下穴张。生成的文件如下细燎,點(diǎn)擊 index.html。

image

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

image

我們再把 @TestAnnotation 注解上的 @Documenet 注解注釋掉再來生成下文檔。此時發(fā)現(xiàn) Test 類中沒有 @TestAnnotation 注解了偿枕。

image

5.4 @Inherited

該注解表示注解是否具有繼承的特性璧瞬。

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

舉個例子來測試下。新建 TestAnnotation 注解益老,F(xiàn)ather 類彪蓬,Son 類,F(xiàn)ather 類使用了該注解捺萌,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酷誓,當(dāng)把 @TestAnnotation 注解上的 @Inherited 注解注釋掉時,輸出 true false态坦,如此可見該注解的作用盐数。

5.5 @Repeatable

JDK8 以前是不支持重復(fù)注解的,同一個地方只能使用同一個注解一次伞梯。 該注解從 JDK8 引入玫氢,該注解類型用于表示其聲明注解的注解類型為可重復(fù)時。 value() 的值表示可重復(fù)注解的類型谜诫,包含注解類型漾峡。

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

舉個例子喻旷,定義 @Activity 和 @Activities 注解生逸,定義 Hncboy 類測試重復(fù)注解。@Activity 注解被 @Repeatable(Activities.class) 注解且预,@Activities 相當(dāng)于一個容器注解槽袄,屬性為 Activity 類型的數(shù)組,通過這樣的方式锋谐,使得 @Activity 注解可以被重復(fù)使用遍尺。

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

六、標(biāo)準(zhǔn)注解

JDK 內(nèi)置的注解有 @Deprecated怀估、@Override狮鸭、@SuppressWarnnings合搅、@SafeVarargs(JDK 7 引入)、@FunctionalInterface(JDK 引入)等歧蕉。接下來介紹下 3 中常用的內(nèi)置注解灾部。

6.1 @Deprecated

注解為 @Deprecated 的類型是不鼓勵程序員使用的元素,通常是因?yàn)檫@樣做很危險惯退,或者是因?yàn)榇嬖诟玫奶娲椒ǘ乃琛.?dāng)在不推薦使用的代碼中使用或覆蓋不推薦使用的程序元素時,編譯器會發(fā)出警告催跪。該注解可以用來修飾構(gòu)造器锁蠕、字段、局部變量懊蒸、方法等類型荣倾。

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

舉個例子,使用 @Deprecated 修飾的元素是不推薦使用的骑丸,編譯器會幫我們將這些類和方法用刪除線標(biāo)記舌仍。直接聲明在包上會報 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 注解我們經(jīng)常用到通危,提示子類需要重寫父類的方法铸豁。方法重寫或?qū)崿F(xiàn)了在父類中聲明的方法時需要加上該注解,該注解用于編譯器檢查重寫的操作是否正確菊碟,保留策略為 RetentionPolicy.SOURCE节芥。

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

6.3 @SuppressWarnings

用來關(guān)閉編譯器生成警告信息,可以用來修飾類逆害、方法头镊、成員變量等,在使用該注解時魄幕,應(yīng)采用就近原則拧晕,如方法產(chǎn)生警告是,應(yīng)該針對方法聲明該注解梅垄,而不是對類聲明,有利于發(fā)現(xiàn)該類的其他警告信息输玷。

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

舉個例子,rawtypes 用于使用泛型時忽略沒有指定相應(yīng)的類型欲鹏,unused 用于沒有使用過的代碼机久。

public class Test {

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

七、自定義注解

自定義注解實(shí)現(xiàn) Spring IOC Bean 實(shí)例創(chuàng)建赔嚎,自定義簡單的注解: @Component膘盖、@Bean 和 @ComponentScan胧弛。

通過什么是反射?這篇文章我們已經(jīng)學(xué)習(xí)到通過反射實(shí)現(xiàn) Spring IOC Bean 實(shí)例的三種創(chuàng)建方式侠畔,不清楚的可以去看下那篇文章结缚。

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("調(diào)用 A 的無參構(gòu)造器");
    }

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

    @MyBean("c")
    public C createCInstance() {
        System.out.println("調(diào)用 A 的實(shí)例方法 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 注解中的包名
  • 然后掃描該包下所有類的全限定名
  • 遍歷類名喘落,判斷改類是否實(shí)現(xiàn) @MyComponent 注解
  • 遍歷方法茵宪,判斷該方法是否實(shí)現(xiàn) @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<>();
        // 掃描包下的所有類并將類的全限定名放進(jìn) classPathSet
        test.doScanPackage(classPathSet, scanPackage);

        // 遍歷掃描包下的所有類
        for (String className : classPathSet) {
            // 通過類的全限定名獲取 Class
            Class<?> clazz = Class.forName(className);
            // 判斷該類是否實(shí)現(xiàn)了 MyComponent 注解
            if (clazz.isAnnotationPresent(MyComponent.class)) {
                // 方式1:通過構(gòu)造器實(shí)例化
                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();
                    // 判斷該方法是否是靜態(tài)方法或?qū)嵗椒?                    if (Modifier.isStatic(method.getModifiers())) {
                        // 方式2:通過靜態(tài)工廠實(shí)例化
                        IOCContainer.putBean(beanName, method.invoke(null));
                    } else {
                        // 方式3:通過實(shí)例工廠實(shí)例化
                        // 首先獲取該類的實(shí)例對象稀火,再調(diào)用實(shí)例方法進(jìn)行實(shí)例化
                        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) {
        // 通過正則表達(dá)式將包名中的 . 替代為 /,并獲取到該路徑的 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 結(jié)尾
                if (!file.getName().endsWith(".class")) {
                    continue;
                }

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

輸出如下:

調(diào)用 A 的無參構(gòu)造器
調(diào)用 A 的靜態(tài)方法 createBInstance
調(diào)用 A 的實(shí)例方法 createCInstance

注:APT——這些處理提取和處理 Annotation 的代碼統(tǒng)稱為 APT(Annotation Processing Tool)赌朋。

Java 編程思想

Java Annotation認(rèn)知

文章同步到公眾號和Github凰狞,有問題的話可以聯(lián)系作者。

燦爛一生 公眾號
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末箕慧,一起剝皮案震驚了整個濱河市服球,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌颠焦,老刑警劉巖斩熊,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異伐庭,居然都是意外死亡粉渠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門圾另,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霸株,“玉大人,你說我怎么就攤上這事集乔∪ゼ” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵扰路,是天一觀的道長尤溜。 經(jīng)常有香客問我,道長汗唱,這世上最難降的妖魔是什么宫莱? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮哩罪,結(jié)果婚禮上授霸,老公的妹妹穿的比我還像新娘巡验。我一直安慰自己,他們只是感情好碘耳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布显设。 她就那樣靜靜地躺著,像睡著了一般藏畅。 火紅的嫁衣襯著肌膚如雪敷硅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天愉阎,我揣著相機(jī)與錄音绞蹦,去河邊找鬼。 笑死榜旦,一個胖子當(dāng)著我的面吹牛幽七,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播溅呢,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼澡屡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了咐旧?” 一聲冷哼從身側(cè)響起驶鹉,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎铣墨,沒想到半個月后室埋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伊约,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年姚淆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屡律。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡腌逢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出超埋,到底是詐尸還是另有隱情搏讶,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布霍殴,位于F島的核電站窍蓝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏繁成。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一淑玫、第九天 我趴在偏房一處隱蔽的房頂上張望巾腕。 院中可真熱鬧面睛,春花似錦、人聲如沸尊搬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽佛寿。三九已至幌墓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冀泻,已是汗流浹背常侣。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弹渔,地道東北人胳施。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像肢专,于是被迫代替她去往敵國和親舞肆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354