這就是java中的註解

對於註解相信你們都不陌生,由於初學者第一個註解就是@Override,用於標識重載方法。在Java EE開發過程當中,註解更是無處不在,像經典的MVC設計模式就至少使用到了4個註解:@Component@Repository@Service@Controller。如今問題來了,爲何要學習註解?它有什麼優勢,能解決什麼問題?經過閱讀本篇文章相信讀者會有一個比較清晰的認識。html

一個常常會遇到的例子

在Java Web開發中,最先是使用XML的配置方式。舉個例子來講,當開發者在Servlet中定義了LoginServlet類,接下來應該是去web.xml配置文件中增長LoginServlet類的訪問映射,可能會使用以下代碼:java

<servlet>
    <servlet-name>LoginServlet</servlet-name>
    <servlet-class>com.envy.servlet.LoginServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
    <url-pattern>/LoginServlet</url-pattern>
  </servlet-mapping>
複製代碼

這樣當用戶訪問諸如http://localhost:8080/LoginServlet連接時,就會執行這個類中定義的方法邏輯。可是針對這種配置方式,有人以爲這種太麻煩了,因而提出了一種新的配置方式:在LoginServlet類上添加@WebServlet註解,並結合value="/LoginServlet"這種屬性值的方式也實現了一樣的目的,毫無疑問後面這種方式使用起來更加方便,而這種方式就是今天的主角---註解。web

註解

什麼是註解?

Java註解(Annotation)自Java1.5引入,用於描述Java代碼的元信息,一般狀況下註解不會直接影響代碼的執行,但某些註解能夠用來影響代碼的執行。初學者可能被這句話給搞暈了,什麼是元信息,一下子不會影響代碼的執行,一下子又能夠影響代碼的執行。其實不用着急,相信經過本文的學習,你必定會對這句話有新的理解。編程

其實註解就像是代碼中的特殊標記,這些特殊標記能夠在類編譯、加載、運行時被讀取,並執行對應的邏輯。設計模式

註解的定義

註解雖然不像class和interface那樣,自一開始就出如今你們的眼前,可是自從它誕生以來,它的地位卻在一直提高。註解也是一種類型,所以它和class、interface同樣值得被人記住。數組

定義註解很是簡單,只需使用@interface關鍵詞聲明便可,好比下面就定義了一個屬於開發者本身的註解@MyAnnotation瀏覽器

public @interface MyAnnotation {
}
複製代碼

是否是以爲和接口很是類似,僅僅在前面添加了一個@符號而已。那麼這個註解如何使用呢?先定義一個Hello類,而後將自定義的@MyAnnotation添加到這個Hello類上面這樣就完成了一個最簡單的註解使用案例,裏面的代碼爲:安全

@MyAnnotation
public class Hello {
}
複製代碼

**因爲自定義的@MyAnnotation內不包含任何成員變量,所以該註解被稱爲標記註解,最多見的標記註解就是@Overried。**可是實際上註解內每每是須要定義成員變量的,所以須要學習另外一個概念:元註解。元註解是對註解進行註解的註解,用於解釋說明被做用的註解的做用,所以它是一種很是基本的註解。bash

在JDK的java.lang.annotation包下定義了6個元註解:@Documented@Inherited@Native@Repeatable@Retention@Target,以下圖所示:微信

後續會依次解釋一下這6個元註解的做用,瞭解和熟悉它們對於提高編程水平有極大的幫助。

元數據

不過在此以前先學習一下帶有成員變量的註解,一般稱之爲元數據。在註解中定義成員變量,它的語法很是相似於方法的聲明:

public @interface MyAnnotation {
    String username();
    String password();
}
複製代碼

這樣就在以前自定義的MyAnnotation註解內添加了兩個成員變量,username和password。請注意在註解上定義的成員變量只能是String、數組、Class、枚舉類和註解。

在註解中定義成員變量實際上是爲了攜帶信息,一般在XML中可能一個標籤中包含子標籤:

<博客>
    <做者>餘思</做者>
    <網址>www.envyzhan.club</網址>
</博客>
複製代碼

子標籤就是一些信息,而這裏在註解中定義的成員變量其實就至關於子標籤。

不過因爲前面的自定義註解@MyAnnotation中定義的成員變量沒有默認值,所以在使用的時候須要添加默認值:

@MyAnnotation(username = "envy",password = "1234")
    public void test(){
    }
複製代碼

一般狀況下會在聲明一個註解的時候,給予它的成員變量一個默認值,這樣便於後續使用:

public @interface MyAnnotation {
    String username() default "envy";
    String password() default "1234";
}
複製代碼

這樣在使用的時候能夠根據實際狀況來選擇是否修改註解的默認值:

@MyAnnotation()
public void test(){
}
複製代碼

注意這裏面存在一個特殊狀況:**當一個註解內只存在一個成員屬性,且屬性值爲value,那麼開發者能夠在聲明該註解的時候不給予它默認值,且在使用的時候不須要指出value屬性,而是直接賦值便可。**舉一個例子來講,如今有一個註解@MyAnnotation,它的內部結構爲:

public @interface MyAnnotation {
    String value();
}
複製代碼

那麼使用的時候能夠採起以下的方式進行:

@MyAnnotation("envy")
    public void test(){
    }
複製代碼

注意這裏的寫法是@MyAnnotation("envy"),固然也能夠採用@MyAnnotation(value="envy")的通用寫法,可是不建議這麼作,由於這自己就是一個語法糖而已。那有人以爲是否是隻要一個註解內只要知足只要一個成員屬性這一條件就能夠呢?往下看,這裏定義了一個@OneAnnotation,它的內部結構爲:

public @interface OneAnnotation {
    String name();
}
複製代碼

而後使用的時候也按照語法糖來寫,那是會報錯的,只要屬性值爲value才能夠,這一點須要引發特別注意:

內置7個元註解

@Documented註解

首先查看一下@Documented註解的源碼信息,以下所示:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
複製代碼

能夠看到@Documented註解自jdk1.5引入,顧名思義@Documented註解是一個用於修飾的註解,被此註解修飾的註解能夠被javadoc等工具文檔化,注意它只負責標記,沒有成員取值。一般javadoc中不包括註解,可是當某個註解被@Documented修飾時,那麼它會被 javadoc工具處理,以後該註解類型信息會存在於生成的文檔中。

舉一個例子,前面自定義了一個註解@MyAnnotation,如今嘗試在該註解上添加@Documented註解,

@Documented
public @interface MyAnnotation {

}
複製代碼

而後新建一個Hello.java文件,在該類上使用該註解:

@MyAnnotation
public class Hello {
}
複製代碼

接着進入dos命令行,切換到該註解所在的文件夾,而後執行javadoc -d doc *.java,該命令的做用是使用javadoc工具生成一個對該目錄下全部java文件的說明文檔。運行命令後能夠發現當前目錄下多了一個doc目錄,進入該目錄,而後使用瀏覽器打開其中的index.html文件:

能夠發現這個文檔中出現了對註釋類型的說明(註解實際上是一種類型,成爲註釋類型,可是一般你們都稱之爲註解)

如今嘗試刪除這個doc目錄,且去掉MyAnnotation註釋上的@Documented註解,再來運行一下上述命令,能夠發現此時生成的index.html文件中就沒有對註釋類型的說明了:

這就是@Documented註解的使用方法,繼續下一個註解。

@Native 註解

首先查看一下@Native註解的源碼信息,以下所示:

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Native {
}
複製代碼

能夠看到@Native註解自jdk1.8引入,用於註釋該字段是一個常量,其值引用native code,能夠發現它的保留時間爲SOURCE階段,這個用的不是不少。

@Target 註解

首先查看一下@Target註解的源碼信息,以下所示:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}
複製代碼

能夠看到@Target註解自jdk1.5引入,用於指明被修飾註解的使用範圍,即用來描述被修飾的註解能夠在哪裏使用。定義在ElementType枚舉類中,一共有10種策略(1.8以前存在前8種,jdk1.8新增後2種):

枚舉常量 註解使用範圍
TYPE 類,接口(包括註解類型),和枚舉
FIELD 字段(包括枚舉常量)
METHOD 方法
PARAMETER 形式參數
CONSTRUCTOR 構造函數
LOCAL_VARIABLE 局部變量
ANNOTATION_TYPE 註解類型
PACKAGE
TYPE_PARAMETER (jdk1.8引入) 類型參數
TYPE_USE (jdk1.8引入) 使用類型

請注意這裏的PACKAGE,不是使用在通常類中,而是用在固定的package-info.java文件中,注意名稱必須是package-info不能修改。舉個例子來講,新建一個WeAnnotation註解,其中的代碼爲:

@Target({ElementType.PACKAGE,ElementType.METHOD})
@Documented
public @interface WeAnnotation {
}
複製代碼

接着再建立一個package-info.java文件,注意必須使用相似於文件建立的方式,不然IDEA不容許建立:

@WeAnnotation
package com.envy.annotation;
複製代碼

這樣就成功的使用了該註解。請注意在@Target註解內定義的成員變量是一個數組,所以必須使用諸如@Target({ElementType.PACKAGE})形式:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}
複製代碼

@Retention 註解

首先查看一下@Retention註解的源碼信息,以下所示:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}
複製代碼

能夠看到@Retention註解自jdk1.5引入,用於指明被修飾註解的保留時間。定義在RetentionPolicy枚舉類中,一共有三種策略:SOURCECLASSRUNTIME

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}
複製代碼

SOURCE,顧名思義就是源碼階段,該註解只存在於.java源文件中,當.Java文件編譯成.class字節碼文件的時候,該註解即被遺棄,被編譯器忽略。CLASS,顧名思義就是字節碼階段,該註解保留到.class字節碼文件中,當.class字節碼文件被jvm加載時即被遺棄,這是默認的生命週期。RUNTIME,註解不只被保存到.class字節碼文件中,且被jvm加載後,仍然存在,所以這個時候就有一種很是厲害的技術--反射。

其實這三個生命週期分別對應於三個階段:.java源文件---> .class字節碼文件 ---> 內存中的字節碼。也就是說生命週期長度順序爲SOURCE< CLASS< RUNTIME,所以前者能做用的地方後者必定也能做用。

通常的,若是開發者想要作一些檢查性的操做,如是否方法重載,禁止編譯器警告等,能夠選擇SOURCE生命週期。若是想要在編譯時進行一些預處理操做,如生成一些輔助代碼等,能夠選擇CLASS生命週期。若是想要在運行時去動態獲取註解信息,那此時必須選擇RUNTIME生命週期。

**反射技術demo演示@Retention@Target註解使用。**前面提到的反射技術就是在運行時動態獲取類的相關信息,下面就結合反射來介紹如何使用@Retention@Target註解,便於demo演示,這裏僅僅獲取類的經常使用信息,如Class、Field和Method 。

**第一步,新建3個自定義註解。**新建myannotation包,並在其中依次建立3個自定義運行時註解,且做用範圍依次爲Class、Field和Method:

//ClassAnno.java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ClassAnno {
    String value();
}

//MethodAnno.java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAnno {
    String name() default "envy";
    String data();
    int age() default 22;
}

//FieldAnno.java
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldAnno {
    int[] value();
}
複製代碼

**第二步,新建測試類RunTimeTest。**新建RunTimeTest測試類,其中的代碼爲:

@ClassAnno("RunTime")
public class RunTimeTest {
    @FieldAnno(value = {66,88})
    public String word = "hello";
    @FieldAnno(value = {1024})
    public int number = 2018;
    @MethodAnno(data = "good",name="envy", age = 22)
    public static void sayHello(){
        System.out.println("this is sayHello method.");
    }
}
複製代碼

**第三步,獲取註解內部信息。**新建GetRunTimeTest類,用於運行時獲取註解內部信息,相應的代碼爲:

public class GetRunTimeTest {
    public static void outInfo(){
        StringBuilder builder = new StringBuilder();
        Class<?> aClass = RunTimeTest.class;
        /**獲取ClassAnno註解信息*/
        builder.append("ClassAnno註解:").append("\n");
        ClassAnno classAnno = aClass.getAnnotation(ClassAnno.class);
        if(classAnno!=null){
            builder.append(Modifier.toString(aClass.getModifiers())).append(" ")
                    .append(aClass.getSimpleName()).append("\n");
            builder.append("註解值爲:").append(classAnno.value()).append("\n\n");
        }

        /**獲取MethodAnno註解信息*/
        builder.append("MethodAnno註解:").append("\n");
        Method[] methods = aClass.getDeclaredMethods();
        for(Method method:methods){
            MethodAnno methodAnno = method.getAnnotation(MethodAnno.class);
            if(methodAnno !=null){
                builder.append(Modifier.toString(method.getModifiers())).append(" ")
                        .append(method.getReturnType().getSimpleName()).append(" ")
                        .append(method.getName()).append("\n");
                builder.append("註解值爲:").append("\n");
                builder.append("name--->").append(methodAnno.name()).append("\n");
                builder.append("data--->").append(methodAnno.data()).append("\n");
                builder.append("age--->").append(methodAnno.age()).append("\n\n");
            }
        }

        /**獲取FieldAnno註解信息*/
        builder.append("FieldAnno註解:").append("\n");
        Field[] fields = aClass.getDeclaredFields();
        for(Field field:fields){
            FieldAnno fieldAnno = field.getAnnotation(FieldAnno.class);
            if(fieldAnno != null){
                builder.append(Modifier.toString(field.getModifiers())).append(" ")
                        .append(field.getType().getSimpleName()).append(" ")
                        .append(field.getName()).append("\n");
                builder.append("註解值爲:").append(Arrays.toString(fieldAnno.value())).append("\n\n");
            }
        }
        System.out.println(builder.toString());
    }

    public static void main(String[] args){
        outInfo();
    }
}
複製代碼

運行該方法後,結果以下:

ClassAnno註解:
public RunTimeTest
註解值爲:RunTime

MethodAnno註解:
public static void sayHello
註解值爲:
name--->envy
data--->good
age--->22

FieldAnno註解:
public String word
註解值爲:[66, 88]

public int number
註解值爲:[1024]
複製代碼

能夠發現使用反射技術,成功的在程序運行時動態地獲取到了註解中的信息,其實在Spring框架內的@Autowried註解就是起這個做用,在類運行時將須要的Bean注入Spring容器,以供後續程序的調用。

@Inherited 註解

首先查看一下@Inherited註解的源碼信息,以下所示:

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

能夠看到@Inherited註解自jdk1.5引入,顧名思義@Inherited註解容許子類繼承父類的註解。舉個例子來講,假設你使用@Inherited註解修飾了自定義的@HeyAnnotation註解,若是此時自定義的@HeyAnnotation註解修飾一個類Book後,而又一個TechBook類又繼承了Book類,那麼此時的TechBook類則默認也擁有你自定義的@HeyAnnotation註解。

HeyAnnotation.java註解中的代碼:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface HeyAnnotation {
}
複製代碼

請注意後續是經過反射來判斷子類中是否存在@HeyAnnotation註解,而註解默認的保留時間爲CLASS,這使得該註解沒法進入RUNTIME時期,所以必須加上@Retention(RetentionPolicy.RUNTIME)。接下來是Book.java文件中的代碼:

@HeyAnnotation
public class Book {
}
複製代碼

而後是Book子類TechBook類中的代碼:

public class TechBook extends Book {
    public static void main(String[] args){
        System.out.println(TechBook.class.isAnnotationPresent(HeyAnnotation.class));
    }
}
複製代碼

而後運行該main方法,能夠發現輸出結果爲true。

@Repeatable 註解

首先查看一下@Repeatable註解的源碼信息,以下所示:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    Class<? extends Annotation> value();
}
複製代碼

能夠看到@Repeatable註解自jdk1.8引入,Repeatable是重複的意思,所以被該註解修飾的註解能夠屢次應用於相同的聲明或類型中。那麼什麼樣的註解能夠被屢次使用呢?固然是註解的值能夠同時取多個了。

舉個例子來講,一個男人多是父親,多是兒子,也有多是丈夫。首先定義一個@Persons註解:

public @interface Persons {
    Person[] value();
}
複製代碼

注意這裏面的成員變量Person也是註解,該註解中的代碼爲:

@Repeatable(Persons.class)
public @interface Person {
    String role() default "";
}
複製代碼

能夠看到在該註解上使用了@Repeatable(Persons.class)註解,這個其實就至關於一個容器註解,容器註解顧名思義就是存放其餘註解的註解,注意容器註解也是一個註解。而後定一個Male類來使用這個@Person註解,相應的代碼爲:

@Person(role = "husband")
@Person(role = "son")
@Person(role = "father")
public class Male {
}
複製代碼

回過頭再來看一下@Persons註解內的代碼:

public @interface Persons {
    Person[] value();
}
複製代碼

能夠看到它的成員變量是一個被@Repeatable(Persons.class)註解修飾的註解,注意它是一個數組。

接下來經過一張圖片來總結一下這6個元註解:

基本註解

在介紹完了元註解後,接下來了解一下Java中默認提供的5個基本註解,它們存在於java.lang包下:@Deprecated@FunctionalInterface@Override@SafeVarargs@SuppressWarnings

請注意不要和java.lang.annotation包下的6個元註解搞混淆了:

@Deprecated註解

首先查看一下@Deprecated註解的源碼信息,以下所示:

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

能夠看到@Deprecated註解自jdk1.5引入,它用來標記過期的元素,這個註解在平常開發中會常常碰到。若是編譯器在編譯階段遇到這個註解時會自動發出提醒警告,用於告訴開發人員正在調用一個過期的元素,如過期的方法、過期的類、過期的成員變量等(能夠參看它的Target註解)。

舉個例子,新建一個Envy類,裏面的代碼爲:

public class Envy {
    @Deprecated
    public void hello(){
        System.out.println("hello");
    }
    public void world(){
        System.out.println("world");
    }
    
    public static void main(String[] args){
        new Envy().hello();
        new Envy().world();
    }
}
複製代碼

能夠看到目前在該hello方法山添加了@Deprecated註解,而後在main方法內新建一個Envy對象去調用這個hello方法,能夠發現編譯器此時自動給該方法中間添加了橫線:

注意這個效果是編譯器添加的,並非程序具備的效果。注意過期方法不是不能使用,而是不建議使用。

@FunctionalInterface註解

首先查看一下@FunctionalInterface註解的源碼信息,以下所示:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
複製代碼

能夠看到@FunctionalInterface註解自jdk1.8引入,它是一個函數式接口註解,這是1.8的一個新特性。所謂的函數式接口 (Functional Interface) 其實就是一個只具備一個方法的普通接口。看到這裏,筆者立馬就想到了多進程中的Runnable接口,它就是一個只含有run方法的函數式接口:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
複製代碼

此時你可能有一個疑惑,函數式接口有什麼用,等下一篇介紹Lambda表達式的時候就會深深體會到它的用處。

@Override註解

首先查看一下@Override註解的源碼信息,以下所示:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
複製代碼

能夠看到@Override註解自jdk1.5引入,它是一個重寫註解,這是使用最多的註解。當某個方法添加@Override註解後,編譯器會去檢查該方法是實現父類的對應方法,能夠避免一些低級錯誤,如單詞拼寫錯誤等。舉個例子,這裏有一個Movie類:

public class Movie {
    public void watch(){
        System.out.println("watch");
    }
}
複製代碼

而後有一個ActionMovie類,該類繼承了Movie類,並實現了其中的watch方法,可是因爲粗心將watch寫成了wath,此時編譯器就會拋出提示信息:

根據這個信息就能知道出錯緣由是方法單詞拼錯了。

@SafeVarargs註解

首先查看一下@SafeVarargs註解的源碼信息,以下所示:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
複製代碼

能夠看到@SafeVarargs註解自jdk1.7引入,它是一個參數安全類型註解,你們也稱之爲Java 7「堆污染」警告,所謂的堆污染其實就是把一個非泛型集合賦值給一個泛型集合的時候,這時就很容易發生堆污染。若是使用@SafeVarargs註解後,它會提醒開發者不要用參數作一些不安全的操做,它的存在會阻止編譯器產生unchecked這樣的警告。這個用的不是不少。

@SuppressWarnings註解

首先查看一下@SuppressWarnings註解的源碼信息,以下所示:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}
複製代碼

能夠看到@SuppressWarnings註解自jdk1.5引入,它是一個抑制編譯器警告註解。前面說過調用被@Deprecated註解修飾的方法後,編譯器會發出提醒,若是開發者想忽略這種警告,那麼能夠在調用時添加@SuppressWarnings註解來實現這個目的:

public class Envy {
    @Deprecated
    public void hello(){
        System.out.println("hello");
    }
    public void world(){
        System.out.println("world");
    }

    @SuppressWarnings("deprecation")
    public static void main(String[] args){
        new Envy().hello();
        new Envy().world();
    }
}
複製代碼

這樣編輯器就不會去檢測hello方法是否過時。一樣經過一張圖片來總結一下這5個基本註解:

自定義註解的使用

將自定義信息注入到方法中

在前面介紹瞭如何在運行時獲取註解中的信息,接下來介紹如何將自定義信息注入到方法中。這裏就再也不自定義獲取註解中信息的方法了,而是直接使用內置的方法。自定義一個@EnvyThink註解,裏面的代碼爲:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
public @interface EnvyThink {
    String name() default "envy";
    int age() default 22;
}
複製代碼

接着新建一個Envy類,其中的代碼爲:

public class Envy {
    @EnvyThink()
    public void hey(String name,int age){
        System.out.println("name--->"+name+"\n"+ "age--->"+age);
    }
}
複製代碼

而後新建一個測試類EnvyTest,裏面的代碼爲:

public class EnvyTest {
    public static void main(String[] args){
        try {
            /**第一步,經過反射獲得該類被註解修飾的方法*/
            Class<?> aClass = Envy.class;
            Method method = aClass.getDeclaredMethod("hey",String.class,int.class);

            /**第二步,經過該方法來獲得註解的信息*/
            EnvyThink envyThink = method.getAnnotation(EnvyThink.class);
            String name = envyThink.name();
            int age = envyThink.age();

            /**第三步,將註解的信息注入到方法內*/
            Object object = aClass.newInstance();
            method.invoke(object,name,age);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
複製代碼

而後運行該方法,能夠知道該方法的結果爲:

name--->envy
age--->22
複製代碼

將自定義對象注入到方法中

能夠看到前面注入的是註解內自定義的成員屬性,接下來嘗試將自定義對象注入到方法中。首先解釋一下什麼是將自定義對象注入到方法中?舉一個例子,首先定義Book實體類,裏面的代碼爲:

public class Book {
    private String name;
    private double price;
    public Book(){};
    public Book(String name,double price){
        this.name=name;
        this.price=price;
    }
    //getter/setter/toString方法
}
複製代碼

其次再定義一個Author類,裏面的代碼爲:

public class Author {
    private Book book;

    public Book getBook(){
        return book;
    }
    public void setBook(Book book){
        this.book = book;
    }
}
複製代碼

你們確定知道這個setBook方法內的參數book對象實際上是外界傳進來的,因此若是須要調用這個方法則必須使用相似以下操做:

public class AuthorTest {
    public static void main(String[] args){
        new Author().setBook(new Book("三國演義",128));
    }
}
複製代碼

如今想經過註解的方式來將該Book對象注入到Author中,也就是以下方式:

@BookAnno(name = "三國演義", price = 128)
public void setBook(Book book){
    this.book = book;
}
複製代碼

應該如何實現呢?往下看,首先定義一個@BookAnno註解,裏面定義兩個屬性name和price(注意屬性數量和名稱必須與Book徹底保持一致),裏面的代碼爲:

@Retention(RetentionPolicy.RUNTIME)
public @interface BookAnno {
    String name() default "三國演義";
    double price() default 128;
}
複製代碼

注意該註解必須添加@Retention(RetentionPolicy.RUNTIME)註解語句,不然沒法經過反射實現相應的功能。

接着定義一個AuthorAnnoTest類,用於實現將Book對象注入到Author的setBook方法中。在該類中,須要經過內省(Introspector)來對JavaBean類屬性、事件進行缺省處理。關於內省的相關介紹,後續會專門出一篇文章進行說明。AuthorAnnoTest類中的代碼爲:

public class AuthorAnnoTest {
  public static void main(String[] args) {
    try {
      /** 1.使用內省機制來獲取想要注入的屬性 */

      /** 第一個參數爲待注入屬性,第二個參數爲該屬性所依附的類 */
      PropertyDescriptor descriptor = new PropertyDescriptor("book", Author.class);

      /** 2.獲取該屬性的setPerson方法(寫方法)*/
      Method method = descriptor.getWriteMethod();

      /** 3.獲取寫方法的註解*/
      Annotation annotation = method.getAnnotation(BookAnno.class);

      /** 4.獲取註解上的信息(注意必須是方法,由於註解內成員變量的定義就是採用方法形式)*/
      Method[] methods = annotation.getClass().getMethods();

      /** 5.獲取待注入屬性的實例對象*/
      Book book = (Book) descriptor.getPropertyType().newInstance();

      /** 6.將註解上的信息添加到book對象上*/
      for (Method meth : methods) {

        /** 7.獲取註解內相關屬性的名稱(注意這裏的name不只僅只是name屬性,而是一個屬性別稱) */
        String name = meth.getName();

        /** 8.判斷Book對象中是否存在各屬性對應的setter方法*/
        try {
          /** 8.1.1 假設存在相對應的setter方法,則採用內省機制來獲取對應的setter方法*/
          PropertyDescriptor descriptor1 = new PropertyDescriptor(name, Book.class);

          /** 8.1.2 獲取各屬性相對應的setter寫方法 */
          Method method1 = descriptor1.getWriteMethod();

          /** 8.1.3 獲取註解中的值*/
          Object o = meth.invoke(annotation, null);

          /** 8.1.4 調用Book對象的setter寫方法,將前面獲取的註解的值設置進去*/
          method1.invoke(book, o);
        } catch (Exception e) {

          /** 8.2 若是Book對象中不存在各屬性對應的setter方法,那麼會跳過當前循環,繼續遍歷註解*/
          continue;
        }
      }

      /** 九、遍歷完成,book對象中的各個setter方法已經成功寫入了註解中的值*/

      /** 9.1 經過setter方法將book對象寫入到author對象中*/
      Author author = new Author();
      method.invoke(author, book);

      /** 10.輸出author對象中的name和price的信息 */
      System.out.println(author.getBook().getName());
      System.out.println(author.getBook().getPrice());
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
複製代碼

運行該方法,能夠獲得輸出結果爲:

三國演義
128.0
複製代碼

接下來總結一下該步驟: 一、使用內省機制來獲取想要注入的屬性;二、獲取該屬性的寫方法;三、獲取寫方法的註解;四、獲取註解上的信息(注意必須是方法,由於註解內成員變量的定義就是採用方法形式);五、獲取待注入屬性的實例對象;六、將註解上的信息添加到對象上;七、調用屬性的寫方法將已添加數據的對象注入到方法中;八、驗證對象是否成功注入方法中。

Spring內置的@Autowried註解就是完成了相似的功能才使得用戶能夠直接在方法中就能使用對象。

將自定義對象注入到屬性中

將自定義對象注入到屬性中,這也是一個常見的使用場景,經過前面的介紹,開發者能夠嘗試照葫蘆畫瓢來實現這一功能。也就是當開發者使用以下方式:

public class Author {
    @BookAnno(name = "三國演義", price = 128)
    private Book book;

    public Book getBook(){
        return book;
    }

    public void setBook(Book book){
        this.book = book;
    }
}
複製代碼

的代碼配置時,咱們依然能夠像前面那樣在author對象中獲取註解的值。接着定義一個AuthorAnnoFiledTest類,用於實現將Book對象注入到Author的book屬性中。須要注意的是首先須要使用反射機制來獲取對應的屬性,其次使用內省機制來獲取對應的寫方法。AuthorAnnoFiledTest類中的代碼爲:

public class AuthorAnnoFiledTest {
    public static void main(String[] args){
        try {
            /** 1.使用反射機制來獲取想要注入的屬性 */
            Field field = Author.class.getDeclaredField("book");

            /** 2.獲取屬性的註解*/
            Annotation annotation = field.getAnnotation(BookAnno.class);

            /** 3.獲取註解上的信息(注意必須是方法,由於註解內成員變量的定義就是採用方法形式)*/
            Method[] methods = annotation.getClass().getMethods();

            /** 4.獲取待注入屬性的實例對象*/
            Book book = (Book) field.getType().newInstance();

            /** 5.將註解上的信息添加到book對象上*/
            for (Method meth : methods) {

                /** 6.獲取註解內相關屬性的名稱(注意這裏的name不只僅只是name屬性,而是一個屬性別稱) */
                String name = meth.getName();

                /** 7.判斷Book對象中是否存在各屬性對應的setter方法*/
                try {
                    /** 7.1.1 假設存在相對應的setter方法,則採用內省機制來獲取對應的setter方法*/
                    PropertyDescriptor descriptor = new PropertyDescriptor(name, Book.class);

                    /** 7.1.2 獲取各屬性相對應的setter寫方法 */
                    Method method1 = descriptor.getWriteMethod();

                    /** 7.1.3 獲取註解中的值*/
                    Object o = meth.invoke(annotation, null);

                    /** 7.1.4 調用Book對象的setter寫方法,將前面獲取的註解的值設置進去*/
                    method1.invoke(book, o);
                } catch (Exception e) {

                    /** 7.2 若是Book對象中不存在各屬性對應的setter方法,那麼會跳過當前循環,繼續遍歷註解*/
                    continue;
                }
            }

            /** 八、遍歷完成,book對象中的各個setter方法已經成功寫入了註解中的值*/

            /** 8.1 經過setter方法將book對象寫入到author對象中*/
            Author author = new Author();
            field.setAccessible(true);
            field.set(author,book);

            /** 10.輸出author對象中的name和price的信息 */
            System.out.println(author.getBook().getName());
            System.out.println(author.getBook().getPrice());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
複製代碼

運行該方法,能夠獲得輸出結果爲:

三國演義
128.0
複製代碼

接下來總結一下該步驟: 一、使用反射機制來獲取想要注入的屬性;二、獲取該屬性的註解;三、獲取註解上的信息(注意必須是方法,由於註解內成員變量的定義就是採用方法形式);四、獲取待注入屬性的實例對象;五、將註解上的信息添加到對象上;六、調用屬性的寫方法將已添加數據的對象注入到方法中;七、驗證對象是否成功注入方法中。

註解總結

接下來對註解進行總結,註解其實就兩個做用:(1)在適當的時候將數據注入到方法、成員變量、類或者其餘組件中;(2)讓編譯器檢查代碼,進而發出提示信息。關於註解就先介紹到這裏,後續會出一篇文章來總結Spring框架中經常使用的註解。

獲取更多內容請關注我的微信公衆帳號:餘思博客,或者微信掃描下方二維碼便可直接關注:

相關文章
相關標籤/搜索