全部知識體系文章,GitHub已收錄,歡迎Star!再次感謝,願你早日進入大廠!java
GitHub地址: https://github.com/Ziphtracks/JavaLearningmanualmysql
註解(Annotation),也叫元數據。一種代碼級別的說明。它是JDK1.5及之後版本引入的一個特性,與類、接口、枚舉是在同一個層次。它能夠聲明在包、類、字段、方法、局部變量、方法參數等的前面,用來對這些元素進行說明,註釋。
- 編寫文檔: 經過代碼裏標識的元數據生成文檔【生成文檔doc文檔】
- 代碼分析: 經過代碼裏標識的元數據對代碼進行分析【使用反射】
- 編譯檢查: 經過代碼裏標識的元數據讓編譯器可以實現基本的編譯檢查【Override等】
編寫文檔git
首先,咱們要知道Java中是有三種註釋的,分別爲單行註釋、多行註釋和文檔註釋。而文檔註釋中,也有@開頭的元註解,這就是基於文檔註釋的註解。咱們可使用javadoc命令來生成doc文檔,此時咱們文檔的內元註解也會生成對應的文檔內容。這就是編寫文檔的做用。
代碼分析程序員
咱們頻繁使用之一,也是包括使用反射來經過代碼裏標識的元數據對代碼進行分析的,此內容咱們在後續展開講解。
編譯檢查github
至於在編譯期間在代碼中標識的註解,能夠用來作特定的編譯檢查,它能夠在編譯期間就檢查出「你是否按規定辦事」,若是不按照註解規定辦事的話,就會在編譯期間飄紅報錯,並予以提示信息。能夠就能夠爲咱們代碼提供了一種規範制約,避免咱們後續在代碼中處理太多的代碼以及功能的規範。好比,@Override註解是在咱們覆蓋父類(父接口)方法時出現的,這證實咱們覆蓋方法是繼承於父類(父接口)的方法,若是該方法稍加改變就會報錯;@FunctionInterface註解是在編譯期檢查是不是函數式接口的,若是不遵循它的規範,一樣也會報錯。
- @Override: 標記在成員方法上,用於標識當前方法是重寫父類(父接口)方法,編譯器在對該方法進行編譯時會檢查是否符合重寫規則,若是不符合,編譯報錯。
- @Deprecated: 用於標記當前類、成員變量、成員方法或者構造方法過期若是開發者調用了被標記爲過期的方法,編譯器在編譯期進行警告。
- @SuppressWarnings: 壓制警告註解,可放置在類和方法上,該註解的做用是阻止編譯器發出某些警告信息。
- @Repeatable: 代表標記的註解能夠屢次應用於相同的聲明或類型,此註解由Java8版本引入。
標記在成員方法上,用於標識當前方法是重寫父類(父接口)方法,編譯器在對該方法進行編譯時會檢查是否符合重寫規則,若是不符合,編譯報錯。
這裏解釋一下@Override註解,在咱們的Object基類中有一個方法是toString方法,咱們一般在實體類中去重寫此方法來達到打印對象信息的效果,這時候也會發現重寫的toString方法上方就有一個@Override註解。以下所示:sql
因而,咱們試圖去改變重寫後的toString方法名稱,將方法名改成toStrings。你會發如今編譯期就報錯了!以下所示:數據庫
那麼這說明什麼呢?這就說明該方法不是咱們重寫其父類(Object)的方法。這就是@Override註解的做用。api
用於標記當前類、成員變量、成員方法或者構造方法過期若是開發者調用了被標記爲過期的方法,編譯器在編譯期進行警告。
咱們解釋@Deprecated註解就須要模擬一種場景了。假設咱們公司的產品,目前是V1.0版本,它爲用戶提供了show1方法的功能。這時候咱們爲產品的show1方法的功能又進行了擴展,打算髮布V2.0版本。可是,咱們V1.0版本的產品須要拋棄嗎?也就是說咱們V1.0的產品功能還繼續讓用戶使用嗎?答案確定是不能拋棄的,由於有一部分用戶是一直用V1.0版本的。若是拋棄了該版本會損失不少的用戶量,因此咱們不能拋棄該版本。這時候,咱們對功能進行了擴展後,發佈了V2.0版本,咱們給予用戶的通知就能夠了,也就是告知用戶咱們在V2.0版本中爲功能進行了擴展。可讓用戶自行選擇版本。數組
可是,除了發佈告知用戶版本狀況以外,咱們還須要在原來版本的功能上給予提示,在上面的模擬場景中咱們須要在show1方法上方加@Deprecated註解給予提示。經過這種方式也告知用戶「這是舊版本時候的功能了,咱們不建議再繼續使用舊版本的功能」,這句話的意思也就正是給用戶作了提示。用戶也會這麼想「奧,這版本的這個功能很差用了,確定有新版本,又更好用的功能。我要去官網查一下下載新版本」,還會有用戶這麼想「我明白了,又更新出更好的功能了,可是這個版本的功能我已經夠用了,不須要從新下載新版本了」。ide
那麼咱們怎麼查看我上述所說的在功能上給予的提示呢?這時候我須要去建立一個方法,而後去調用show1方法,並查看調用時它是如何提示的。
圖已經貼出來了,你是否發現的新舊版本功能的異同點呢?很明顯,在方法中的提示是在調用的方法名上加了一道橫線把該方法劃掉了。這就體現了show1方法過期了,已經不建議使用了,咱們爲你提供了更好的。
回想起來,在咱們的api中也會有方法是過期的,好比咱們的Date日期類中的方法有不少都已通過時了。以下圖:
如你所見,是否是有不少方法都過期了呢?那它的方法上是加了@Deprecated註解嗎?來跟着個人腳步,我帶大家看一下。
咱們已經知道的Date類中的這些方法已是過期的了,若是咱們使用該方法並執行該程序的話。執行的過程當中就會提示該方法已過期的內容,可是隻是提示,並不影響你使用該方法。以下:
OK!這也就是@Deprecated註解的做用了。
壓制警告註解,可放置在類和方法上,該註解的做用是阻止編譯器發出某些警告信息,該註解爲單值註解,只有 一個value參數,該參數爲字符串數組類型,參數值經常使用的有以下幾個。
- unchecked:未檢查的轉化,如集合沒有指定類型還添加元素
- unused:未使用的變量
- resource:有泛型未指定類型
- path:在類路徑,原文件路徑中有不存在的路徑
- deprecation:使用了某些不同意使用的類和方法
- fallthrough:switch語句執行到底沒有break關鍵字
- rawtypes:沒有寫泛型,好比: List list = new ArrayList();
- all:所有類型的警告
壓制警告註解,顧名思義就是壓制警告的出現。咱們都知道,在Java代碼的編寫過程當中,是有不少黃色警告出現的。可是我不知道你的導師是否教過你,程序員只須要處理紅色的error,不須要理會黃色的warning。若是你的導師說過此問題,那是有緣由的。由於在你學習階段,咱們認清處理紅色的error便可,這樣能夠減輕你學習階段在腦部的記憶內容。若是你剛剛加入學習Java的隊列中,須要大腦記憶的東西就有太多了,也就是咱們目前不須要額外記憶其餘的東西,只記憶重點便可。至於黃色warning嘛,在你的學習過程當中慢慢就會有所瞭解的,而不是死記硬背的。
那爲了解釋@SuppressWarnings註解,咱們還使用上一個例子,由於在那個例子中就有黃色的warning出現。
而每個黃色的warning都會有警告信息的。好比,這一個圖中的警告信息,就告知你show2()方法沒有被使用,簡單來講,你建立的show2方法,可是你在代碼中並無調用過此方法。之後你便會遇到各類各樣黃色的warning。而後, 咱們就可使用不一樣的註解參數來壓制不一樣的註解。可是在該註解的參數中,提供了一個all參數能夠壓制所有類型的警告。而這個註解是須要加到類的上方,並賦予all參數,便可壓制全部警告。以下:
咱們加入註解並賦予all參數後,你會發現use方法和show2方法的警告沒有了,實際上導Date包的警告還在,由於咱們Date包導入到了該類中,可是咱們並無建立Date對象,也就是並無寫入Date在代碼中,你也會發現那一行是灰色的,也就證實了咱們沒有去使用導入這個包的任何信息的說法,出現這種狀況咱們就須要把這個沒有用的導包內容刪除掉,使用Ctrl + X
刪除導入沒有用到的包便可。還有一種辦法就是在包的上方修飾壓制警告註解,可是我認爲在一個沒有用的包上加壓制註解是毫無心義的,因此,咱們直接刪除就好。
而後,咱們還見到上圖,註解那一行出現了警告信息提示。這一行的意思是冗餘的警告壓制。這就是說咱們壓制如下的警告並無什麼意義而形成的冗餘,可是若是咱們使用了該類並作了點什麼的話,壓制註解的冗餘警告就會消失,畢竟咱們使用了該類,此時就不會早場冗餘了。
上述解釋@SuppressWarnings註解也差很少就這些了。OK,繼續向下看吧。持續爲你們講解。
@Repeatable 代表標記的註解能夠屢次應用於相同的聲明或類型,此註解由Java8版本引入。咱們知道註解是不能重複定義的,其實該註解就是一個語法糖,它能夠重複多此使用,更適用於咱們的特殊場景。
首先,咱們先建立一個能夠重複使用的註解。
package com.mylifes1110.anno;
import java.lang.annotation.Repeatable;
@Repeatable(Hour.class)
public @interface Hours {
double[] hours() default 0;
}
你會發現註解要求傳入的值是一個類對象,此類對象就須要傳入另一個註解,這裏也就是另一個註解容器的類對象。咱們去建立一下。
package com.mylifes1110.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//容器
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Hour {
Hours[] value();
}
其實,這兩個註解的套用,就是將一個普通的註解封裝了一個可重複使用的註解,來達到註解的複用性。最後,咱們建立一下測試類,隨後帶你去看一下源碼。
package com.mylifes1110.java;
import com.mylifes1110.anno.Hours;
@Hours(hours = 4)
@Hours(hours = 4.5)
@Hours(hours = 2)
public class Worker {
public static void main(String[] args) {
//經過Hours註解類型來獲取Worker中的值數組對象
Hours[] hours = Worker.class.getAnnotationsByType(Hours.class);
//遍歷數組
for (Hours h : hours) {
System.out.println(h);
}
}
}
測試類,是一個工人測試類,該工人使用註解記錄早中晚的工做時間。測試結果以下:
而後咱們進入到源碼一探究竟。
咱們發現進入到源碼後,就只看見一個返回值爲類對象的抽象方法。這也就驗證了該註解只是一個可實現重複性註解的語法糖而已。
註解能夠根據註解參數分爲三大類:
- 標記註解: 沒有參數的註解,僅用自身的存在與否爲程序提供信息,如@Override註解,該註解沒有參數,用於表示當前方法爲重寫方法。
- 單值註解: 只有一個參數的註解,若是該參數的名字爲value,那麼能夠省略參數名,如 @SuppressWarnings(value = "all"),能夠簡寫爲@SuppressWarnings("all")。
- 完整註解: 有多個參數的註解。
說到@Override註解是一個標記註解,那咱們進入到該註解的源碼查看一下。從上往下看該註解源碼,發現它繼承了導入了
java.lang.annotation.*
,也就是有使用到該包的內容。而後下面就又是兩個看不懂的註解,其實發現註解的定義格式是public修飾的@Interface,最終看到該註解中方法體並無任何參數,也就是隻起到標記做用。
在上面咱們用到的@SuppressWarnings註解就是一個單值註解。那咱們進入到它的源碼看一下是怎麼個狀況。其實,和標記註解比較,它就多一個value參數而已,而這就是單值註解的必要條件,即只有一個參數。而且這一個參數爲value時,咱們能夠省略value。
上述兩個類型註解講解完,至於完整註解嘛,這下就能更明白了。其中的方法體就是有多個參數而已。
格式: public @Interface 註解名 {屬性列表/無屬性}注意: 若是註解體中無任何屬性,其本質就是標記註解。可是與其標註註解還少了上邊修飾的元註解。
以下,這就是一個註解。可是它與jdk自定義註解有點區別,jdk自定義註解的上方還有註解來修飾該註解,而那註解就叫作元註解。元註解我會在後面詳細的說到。
這裏咱們的確不知道@Interface是什麼,那咱們就把自定義的這個註解反編譯一下,看一下反編譯信息。反編譯操做以下:
反編譯後的反編譯內容以下:
public interface com.mylifes1110.anno.MyAnno extends java.lang.annotation.Annotation {
}
首先,看過反編譯內容後,咱們能夠直觀的得知他是一個接口,由於它的public修飾符後面的關鍵字是interface。
其次,咱們發現MyAnno
這個接口是繼承了java.lang.annotation
包下的Annotation
接口。
因此,咱們能夠得知註解的本質就是一個接口,該接口默認繼承了Annotation
接口。
既然,是繼承的Annotation
接口,那咱們就去進入到這個接口中,看它定義了什麼。如下是我抽取出來的接口內容。咱們發現它看似很常見,其實它們不是很經常使用,做爲了解便可。
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
最後,咱們的註解中也是能夠寫有屬性的,它的屬性不一樣於普通的屬性,它的屬性是抽象方法。既然註解也是一個接口,那麼咱們能夠說接口體中能夠定義什麼,它一樣也能夠定義,而它的修飾符與接口同樣,也是默認被public abstract
修飾。
而註解體中的屬性也是有要求的。其屬性要求以下:
屬性的返回值類型必須是如下幾種:
- 基本數據類型
- String類型
- 枚舉類型
- 註解
- 以上類型的數組
- 注意: 在這裏不能有void的無返回值類型和以上類型之外的類型
定義的屬性,在使用時須要給註解中的屬性賦值
- 若是定義屬性時,使用
default
關鍵字給屬性默認初始化值,則使用註解時能夠不爲屬性賦值,它取的是默認值。若是爲它再次傳入值,那麼就發生了對原值的覆蓋。- 若是隻有一個屬性須要賦值,而且屬性的名稱爲value,則賦值時value能夠省略,能夠直接定義值
- 數組賦值時,值使用
{}
存儲值。若是數組中只有一個值,則能夠省略{}
。
屬性返回值既然有以上幾種,那麼我就在這裏寫出這幾種演示一下是如何寫的。
首先,定義一個枚舉類和另一個註解備用。
package com.mylifes1110.enums;
public enum Lamp {
RED, GREEN, YELLOW
}
package com.mylifes1110.anno;
public @interface MyAnno2 {
}
其次,咱們來定義上述幾種類型,以下:
package com.mylifes1110.anno;
import com.mylifes1110.enums.Lamp;
public @interface MyAnno {
//基本數據類型
int num();
//String類型
String value();
//枚舉類型
Lamp lamp();
//註解類型
MyAnno2 myAnno2();
//以上類型的數組
String[] values();
Lamp[] lamps();
MyAnno2[] myAnno2s();
int[] nums();
}
這裏咱們演示一下,首先,咱們使用該註解來進行演示。
package com.mylifes1110.anno;
public @interface MyAnno {
//基本數據類型 int num(); //String類型 String value();
}
隨後建立一個測試類,在類的上方寫上註解,你會發現,註解的參數中會讓你寫這兩個參數(int、String)。
此時,傳參是這樣來作的。格式爲:名稱 = 返回值類型參數
。以下:
上述所說,若是使用default關鍵字給屬性默認初始化值,就不須要爲其參數賦值,若是賦值的話,就把默認初始化的值覆蓋掉了。
固然還有一個規則,若是隻有一個屬性須要賦值,而且屬性的名稱爲value,則賦值時value能夠省略,能夠直接定義值。那麼,咱們的num已經有了默認值,就能夠不爲它傳值。咱們發現,註解中定義的屬性就剩下了一個value屬性值,那麼咱們就能夠來演示這個規則了。
這裏,我並無寫屬性名稱value,而是直接爲value賦值。若是我將num的default關鍵字修飾去掉呢,那意思也就是說在使用該註解時必須爲num賦值,這樣能夠省略value嗎?那咱們看一下。
結果,就是咱們所想的,它報錯了,必須讓咱們給num賦值。其實想一想這個規則也是很容易懂的,定義一個爲value的值,就能夠省略其value名稱。若是定義多個值,它們能夠省略名稱就沒法區分定義的是那個值了,關鍵是還有數組,數組內定義的是多個值呢,對吧。
這裏咱們演示一下,上述的多種返回值類型是如何賦值的。這裏咱們定義這幾個參數來看一下,是如何爲屬性賦值的。
num是一個int基本數據類型,即num = 1
value是一個String類型,即value = "str"
lamp是一個枚舉類型,即lamp = Lamp.RED
myAnno2是一個註解類型,即myAnno2 = @MyAnno2
values是一個String類型數組,即values = {"s1", "s2", "s3"}
values是一個String類型數組,其數組中只有一個值,即values = "s4"
注意: 值與值之間是,
隔開的;數組是用{}
來存儲值的,若是數組中只有一個值能夠省略{}
;枚舉類型是枚舉名.枚舉值
元註解就是用來描述註解的註解。通常使用元註解來限制自定義註解的使用範圍、生命週期等等。
而在jdk的中java.lang.annotation包中定義了四個元註解,以下:
元註解
描述
@Target
指定被修飾的註解的做用範圍
@Retention
指定了被修飾的註解的生命週期
@Documented
指定了被修飾的註解是能夠Javadoc等工具文檔化
@Inherited
指定了被修飾的註解修飾程序元素的時候是能夠被子類繼承的
@Target 指定被修飾的註解的做用範圍。其做用範圍能夠在源碼中找到參數值。
屬性
描述
CONSTRUCTOR
用於描述構造器
FIELD(經常使用)
用於描述屬性
LOCAL_VARIABLE
用於描述局部變量
METHOD(經常使用)
用於描述方法
PACKAGE
用於描述包
PARAMETER
用於描述參數
TYPE(經常使用)
用於描述類、接口(包括註解類型) 或enum聲明
ANNOTATION_TYPE
用於描述註解類型
TYPE_USE
用於描述使用類型
因而可知,該註解體內只有一個value屬性值,可是它的類型是一個ElementType數組。那咱們進入到這個數組中繼續查看。
進入到該數組中,你會發現他是一個枚舉類,其中定義了上述表格中的各個屬性。
瞭解了@Target的做用和屬性值後,咱們來使用一下該註解。首先,咱們要先用該註解來修飾一個自定義註解,定義該註解的指定做用在類上。以下:
而你觀察以下測試類,咱們把註解做用在類上時是沒有錯誤的。而當咱們的註解做用在其餘地方就會報錯。這也就說明了,咱們@Target的屬性起了做用。
注意: 若是咱們定義多個做用範圍時,也是能夠省略該參數名稱了,由於該類型是一個數組,雖然能省略名稱可是,咱們還須要用{}
來存儲。
@Retention 指定了被修飾的註解的生命週期
屬性
描述
RetentionPolicy.SOURCE
註解只在源碼階段保留,在編譯器進行編譯時它將被丟棄忽視。
RetentionPolicy.CLASS
註解只被保留到編譯進行時的class文件,但 JVM 加載class文件時候被遺棄,也就是在這個階段不會讀取到該class文件。
RetentionPolicy.RUNTIME(經常使用)
註解能夠保留到程序運行的時候,它會被加載進入到 JVM 中,因此在程序運行時能夠獲取到它們。
注意: 咱們經常使用的定義便是RetentionPolicy.RUNTIME
,由於咱們使用反射來實現的時候是須要從JVM中獲取class類對象並操做類對象的。
首先,咱們要了解反射的三個生命週期階段,這部份內容我在Java反射機制
中也是作了很是詳細的說明,有興趣的小夥伴能夠去看看我寫的Java反射機制,相信你在其中也會有所收穫。
這裏我再次強調一下這三個生命週期是源碼階段
- > class類對象階段
- > Runtime運行時階段
。
那咱們進入到源碼,看看@Retention註解中是否有這些參數。
咱們看到該註解中的屬性只有一個value,而它的類型是一個RetentionPolicy類型,咱們進入到該類型中看看有什麼參數,是否與表格中的參數相同呢?
至於該註解怎麼使用,實際上是相同的,用法以下:
這就證實了咱們的註解能夠保留到Runtime運行階段,而咱們在反射中大多數是定義到Runtime運行時階段的,由於咱們須要從JVM中獲取class類對象並操做類對象。
@Documented 指定了被修飾的註解是能夠Javadoc等工具文檔化
@Documented註解是比較好理解的,它是一個標記註解。被該標記註解標記的註解,生成doc文檔時,註解是能夠被加載到文檔中顯示的。
還拿api中過期的Date中的方法來講,在api中顯示Date中的getYear方法是這樣的。
正如你看到的,註解在api中顯示了出來,證實該註解是@Documented註解修飾並文檔化的。那咱們就看看這個註解是否被@Documented修飾吧。
而後,咱們發現該註解的確是被文檔化了。因此在api中才會顯示該註解的。若是不信,你能夠本身使用javadoc
命令來生成一下doc文檔,看看被該註解修飾的註解是否存在。
至於Javadoc文檔生成,我在javadoc文檔生成一文中有過詳細記載,你們能夠進行參考,生成doc文檔查看。
@Inherited 指定了被修飾的註解修飾程序元素的時候是能夠被子類繼承的
首先進入到源碼中,咱們也能夠清楚的知道,該註解也是一個標記註解。並且它也是被文檔化的註解。
其次,咱們去在自定義註解中,標註上@Inherited註解。
演示@Inherited註解,我須要建立兩個類,同時兩個類中有一層的繼承關係。以下:
咱們在Person類中標記了@MyAnno註解,因爲該註解被@Inherited註解修飾,咱們就能夠得出繼承於Person類的Student類也一樣被@MyAnno註解標記了,若是你要獲取該註解的值的話,確定獲取的也是父類上註解值的那個"1"。
自定義註解
package com.mylifes1110.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @InterfaceName Sign
* @Description 描述須要執行的類名和方法名
* @Author Ziph
* @Date 2020/6/6
* @Since 1.8
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sign {
String methodName(); String className();
}
Cat
package com.mylifes1110.java;
/**
* @ClassName Cat
* @Description 描述一隻貓的類
* @Author Ziph
* @Date 2020/6/6
* @Since 1.8
*/
public class Cat {
/\* \* @Description 描述一隻貓吃魚的方法 \* @Author Ziph \* @Date 2020/6/6 \* @Param \[\] \* @return void \*/ public void eat() { System.out.println("貓吃魚"); }
}
準備好,上述代碼後,咱們就能夠開始編寫使用反射技術來解析註解的測試類。以下:
首先,咱們先經過反射來獲取註解中的methodName和className參數。
package com.mylifes1110.java;
import com.mylifes1110.anno.Sign;
/**
* @ClassName SignTest
* @Description 要求建立cat對象並執行其類中eat方法
* @Author Ziph
* @Date 2020/6/6
* @Since 1.8
*/
@Sign(className = "com.mylifes1110.java.Cat", methodName = "eat")
public class SignTest {
public static void main(String\[\] args) { //獲取該類的類對象 Class<SignTest> signTestClass = SignTest.class; //獲取類對象中的註解對象 //原理其實是在內存中生成了一個註解接口的子類實現對象 Sign sign = signTestClass.getAnnotation(Sign.class); //調用註解對象中定義的抽象方法(註解中的屬性)來獲取返回值 String className = sign.className(); String methodName = sign.methodName(); System.out.println(className); System.out.println(methodName); }
}
此時的打印結果證實咱們已經成功獲取到了該註解的兩個參數。
注意: 獲取類對象中的註解對象時,其原理其實是在內存中生成了一個註解接口的子類實現對象並返回的字符串內容。以下:
public class SignImpl implements Sign {
public String methodName() { return "eat"; } public String className() { return "com.mylifes1110.java.Cat"; }
}
繼續編寫咱們後面的代碼,代碼完整版以下:
完整版代碼
package com.mylifes1110.java;
import com.mylifes1110.anno.Sign;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @ClassName SignTest
* @Description 要求建立cat對象並執行其類中eat方法
* @Author Ziph
* @Date 2020/6/6
* @Since 1.8
*/
@Sign(className = "com.mylifes1110.java.Cat", methodName = "eat")
public class SignTest {
public static void main(String\[\] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { //獲取該類的類對象 Class<SignTest> signTestClass = SignTest.class; //獲取類對象中的註解對象 //原理其實是在內存中生成了一個註解接口的子類實現對象 Sign sign = signTestClass.getAnnotation(Sign.class); //調用註解對象中定義的抽象方法(註解中的屬性)來獲取返回值 String className = sign.className(); String methodName = sign.methodName(); //獲取className名稱的類對象 Class<?> clazz = Class.forName(className); //建立對象 Object o = clazz.newInstance(); //獲取methodName名稱的方法對象 Method method = clazz.getMethod(methodName); //執行該方法 method.invoke(o); }
}
執行結果
執行後成功的調用了eat方法,並打印了貓吃魚的結果,以下:
首先,咱們在使用JDBC的時候是須要經過properties文件來獲取配置JDBC的配置信息的,此次咱們經過自定義註解來獲取配置信息。其實使用註解並無用配置文件好,可是咱們須要瞭解這是怎麼作的,獲取方法也是魚使用反射機制解析註解,所謂「萬變不離其宗」,它就是這樣的。
package com.mylifes1110.java.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @InterfaceName DBInfo
* @Description 給予註解聲明週期爲運行時並限定註解只能用在類上
* @Author Ziph
* @Date 2020/6/6
* @Since 1.8
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBInfo {
String driver() default "com.mysql.jdbc.Driver"; String url() default "jdbc:mysql://localhost:3306/temp?useUnicode=true&characterEncoding=utf8"; String username() default "root"; String password() default "123456";
}
爲了代碼的健全我也在裏面加了properties文件獲取鏈接的方式。
package com.mylifes1110.java.utils;
import com.mylifes1110.java.anno.DBInfo;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* @ClassName DBUtils
* @Description 數據庫鏈接工具類
* @Author Ziph
* @Date 2020/6/6
* @Since 1.8
*/
@DBInfo()
public class DBUtils {
private static final Properties PROPERTIES = new Properties(); private static String driver; private static String url; private static String username; private static String password; static { Class<DBUtils> dbUtilsClass = DBUtils.class; boolean annotationPresent = dbUtilsClass.isAnnotationPresent(DBInfo.class); if (annotationPresent) { /\*\* \* DBUilts類上有DBInfo註解,並獲取該註解 \*/ DBInfo dbInfo = dbUtilsClass.getAnnotation(DBInfo.class);
// System.out.println(dbInfo);
driver = dbInfo.driver(); url = dbInfo.url(); username = dbInfo.username(); password = dbInfo.password(); } else { InputStream inputStream = DBUtils.class.getResourceAsStream("db.properties"); try { PROPERTIES.load(inputStream); } catch (IOException e) { e.printStackTrace(); } } try { Class.forName(driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() { try { return DriverManager.getConnection(url, username, password); } catch (SQLException throwables) { throwables.printStackTrace(); } return null; } public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) { try { if (resultSet != null) { resultSet.close(); resultSet = null; } if (statement != null) { statement.close(); statement = null; } if (connection != null) { connection.close(); connection = null; } } catch (SQLException throwables) { throwables.printStackTrace(); } }
}
package com.mylifes1110.java.test;
import com.mylifes1110.java.utils.DBUtils;
import java.sql.Connection;
/**
* @ClassName GetConnectionDemo
* @Description 測試鏈接是否能夠獲取到
* @Author Ziph
* @Date 2020/6/6
* @Since 1.8
*/
public class GetConnectionDemo {
public static void main(String\[\] args) { Connection connection = DBUtils.getConnection(); System.out.println(connection); }
}
爲了證實獲取的鏈接是由註解的配置信息獲取到的鏈接,我將properties文件中的全部配置信息刪除後測試的。
我不清楚小夥伴們是否瞭解,Junit單元測試。@Test是單元測試的測試方法上方修飾的註解。此註解的核心原理也是由反射來實現的。若是有小夥伴不知道什麼是單元測試或者對 自定義@MyTest註解實現單元測試感興趣的話,能夠點進來看看哦!