感謝作者:本文轉自:https://www.cnblogs.com/yangming1996/p/9295168.html
以前,xml是各大框架的青睞者,他以松耦合的方式玩是完成了框架中幾乎所有的配置,但是隨着項目越來越龐大,xml的內容也越來越復雜,維護成本也越來越高,於是人們提出一種高耦合的配置方式 注解,方法上可以注解,類上可以注解,字段屬性上也可以注解。反正幾乎配置的地方都可以進行注解。
注解的本質
java.lang.annotation.Annotation接口中有這么一句話,用來描述注解
所有的注解都繼承自於Annotation
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
這是注解Override的定義
public interface Override extends Annotation{ }
沒錯注解的本質就是一個繼承了Annotation接口的接口。有管這一點,你可以反編譯任意一個注解類,你會得到結果,一個注解准確的含義就是一個特殊的注釋而已,如果沒有解析他的代碼,他可能連注解都不如
而解析一個類或者方法的注解往往兩種形式,一種是編譯器直接掃描,一種是運行期間反射。反射的事情我們待會說,而編譯器的掃描指得是編譯器對java代碼編譯字節碼的過程中會檢測到某個類或者方法的注解修飾。這是他就睡對這些注解進行某些特殊的處理,典型的注解就是@Override,一旦編譯器檢測到某個方法修飾了@Override注解,編譯器就會檢查當前方法的方法的方法名是否真正重寫了父類的某個方法。也就是比較父類中是否有一個同樣的方法簽名
這一種情況僅僅適用於編譯器已經成熟的注解類,比如JDK內置的幾個注解類,而你自定義的的注解,編譯器是不知道你這個主機的作用的。當然不知道該如何處理。往往只會根據該注解的作用范圍來選擇是否編譯進行字節碼文件,僅此而已。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
這是我們@Override注解的定義,你可以看到其中的@Target,@Retention兩個注解就是我們所謂的元注解,元注解一般用於指定某個注解的生命周期以及作用目標等信息。
java中一下元注解
. @Target:注解的作用目標
.@Retrntion:注解的生命周期
.@Documented:注解是否應該被包含在Javadoc中
.@Iherited:是否允許子類繼承該注解
@Target的定義如下 @Documented @Retention(RetentionPolilcy.RUNNING) @Target(ElementType.ANNOTATION_TYPE) public @interface Target{ ElementTypep[] value(); }
我們可以通過如下的方式傳值
@Target(value = {ElementType.FIELD})
這個Target注解修飾的注解將只能作用在成員字段上,不能用英語修飾方法或者類。其中,ElementType是枚舉類型,有一下一些值:
- ElementType.TYPE:允許被修飾的注解作用在類、接口和枚舉上
- ElementType.FIELD:允許作用在屬性字段上
- ElementType.METHOD:允許作用在方法上
- ElementType.PARAMETER:允許作用在方法參數上
- ElementType.CONSTRUCTOR:允許作用在構造器上
- ElementType.LOCAL_VARIABLE:允許作用在本地局部變量上
- ElementType.ANNOTATION_TYPE:允許作用在注解上
- ElementType.PACKAGE:允許作用在包上
@Retention用於指明當前注解的生命周期,他的定義如下:
@Documented @Retention(RententionPolicy.RUNNINT) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention{ RetentionPolicy value(); }
同樣的他又一個value屬性
@Retention(value = RetentionPolicy.RUNTIME)
注解與反射
上訴內容我們介紹了注解使用上的細節,簡單提到,注解的本質是繼承Annotation接口的接口。那么從虛擬機層面看注解是個什么東西
我們自己定義一個注解類。
@Target(value = {ElementType.FIELD,ElementType.METHOD}) @Retention(value = RetentionPolicy.RUNNINT) public @interface Hello{ String value(); }
這里我們指定了Hello這個注解只能修飾字段和方法,並且修飾永遠存貨,以便我們反射獲取
之前我們說過,虛擬機規范定義了一系列和注解相關的屬性表,也就是說,無論是字段、方法或是類本身,如果被注解修飾了,就可以被寫進字節碼文件。屬性表有以下幾種:
- RuntimeVisibleAnnotations:運行時可見的注解
- RuntimeInVisibleAnnotations:運行時不可見的注解
- RuntimeVisibleParameterAnnotations:運行時可見的方法參數注解
- RuntimeInVisibleParameterAnnotations:運行時不可見的方法參數注解
- AnnotationDefault:注解類元素的默認值
給大家看虛擬機的這幾個注解相關的屬性表的目的在於,讓大家從整體上構建一個基本的影響,注解在字節碼文件中式如何存儲的。所以,對於一個類或者接口來說,class 類中提供了一下一些方法用於發射注解
- getAnnotation:返回指定的注解
- isAnnotationPresent:判定當前元素是否被指定注解修飾
- getAnnotations:返回所有的注解
- getDeclaredAnnotation:返回本元素的指定注解
- getDeclaredAnnotations:返回本元素的所有注解,不包含父類繼承而來的
方法、字段中相關反射的方法基本式類似的,這里不做過多陳述,下面我們看一個完整的例子。
public class Test{ @Hello("hello") public static void main(String []args) throw NOSuchMethodException{ Class cls = Test.class; Method method = clas.getMethod("main",String[].class); Hello hello = method.getAnootation(Hello.class); } }
我們說過,注解本質上式繼承了Annotation 接口的接口,而當你通過反射,而你通過反射獲取getAnnotation方法中獲取一個注解的實例的時候,其實JDK是通過動態代理機制生成一個實現我們注解接口的代理類。我們運行程序后,會看到輸出目錄中又這么一個代理類,反編譯之后是這樣的
代理類實現接口Hello並沖洗所有的方法。包括Hello從Annotation接口繼承而來的方法。而這個InvocationHandler實例是誰?
AnnotationInvocationHandler是JAVA中專門用於吃醋里注解Handler,而這個類的涉及也非常由意思。
這里有一個memberVallues,他是一個Map鍵值對,鍵是我們注解屬性名稱,值就是該屬性當初被賦予上的值。
而這個invoke方法就很有意思了,大家注意看,我們的代理類代理了Hello接口中所有的方法,所以對於代理類中的任何方法的調用都會被傳遞到這里來。
var2指向被調用方法實例,而治理首先用變量var4獲取該方法的簡明名稱,接着switch結構判斷當前的調用方法時誰,如果時Annotation中
,將 var7 賦上特定的值。
如果當前調用的方法是 toString,equals,hashCode,annotationType 的話,AnnotationInvocationHandler 實例中已經預定義好了這些方法的實現,直接調用即可。
那么假如 var7 沒有匹配上這四種方法,說明當前的方法調用的是自定義注解字節聲明的方法,例如我們 Hello 注解的 value 方法。這種情況下,將從我們的注解 map 中獲取這個注解屬性對應的值
接下來我們看一個注解類的簡單實現以及琢磨他的原理時怎么回事。