注解在表面上的意思,只是標記一下這一部分,最好的注解就是代碼自身。而在java上,由於注解的特殊性,可以通過反射API獲取,這種特性使得注解被廣泛應用於各大框架,用於配置內容,代替xml文件配置。
要學會注解的使用,最簡單的就是定義自己的注解,所以需要先了解一個java的元注解
1、元注解--注解的注解
元注解的作用就是負責注解其他注解,在java1.6上,只有四個元注解:@Target、@Retention、@Documented、@Inherited。在java1.8上,多了@Native與@Repeatable。下面先說說這幾個元注解
(1)、Documented
這個純粹是語義元注解,指示某一類型的注解將通過 javadoc 和類似的默認工具進行文檔化。應使用此類型來注解這些類型的聲明:其注解會影響由其客戶端注解的元素的使用。如果類型聲明是用 Documented 來注解的,則其注解將成為注解元素的公共 API 的一部分。被這個注解注解的注解(真拗口...)會在自動生成api文檔時加載文檔中。他的聲明時這樣的:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }
(2)、Inherited
指示注解類型被自動繼承。如果在注解類型聲明中存在 Inherited 元注解,並且用戶在某一類聲明中查詢該注解類型,同時該類聲明中沒有此類型的注解,則將在該類的超類中自動查詢該注解類型。此過程會重復進行,直到找到此類型的注解或到達了該類層次結構的頂層 (Object) 為止。如果沒有超類具有該類型的注解,則查詢將指示當前類沒有這樣的注解。
注意,如果使用注解類型注解類以外的任何事物,此元注解類型都是無效的。還要注意,此元注解僅促成從超類繼承注解;對已實現接口的注解無效。
即一個類中,沒有@Father的注解,但是這個類的父類有@Father注解,且@Father注解被@Inherited注解,則在使用反射獲取子類@Father注解時,是可以獲取到父類的@Father注解的。如果一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }
(3)、Retention
指示注解類型的注解要保留多久。如果注解類型聲明中不存在 Retention 注解,則保留策略默認為 RetentionPolicy.CLASS。只有元注解類型直接用於注解時,Target 元注解才有效。如果元注解類型用作另一種注解類型的成員,則無效。
某些Annotation僅出現在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因為Annotation與class在使用上是被分離的)。使用這個meta-Annotation可以對 Annotation的“生命周期”限制。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); }
value值為類型為RetentionPolicy,是本包的一個枚舉類型,包含三個值:
RetentionPolicy.SOURCE 只在源代碼中出現,編譯器要丟棄的注解。
RetentionPolicy.CLASS 編譯器將把注解記錄在類文件中,但在運行時 VM 不需要保留注解。
RetentionPolicy.RUNTIME 編譯器將把注解記錄在類文件中,在運行時 VM 將保留注解,因此可以反射性地讀取。
PS:當注解中只有一個屬性(或只有一個屬性沒有默認值),且該屬性為value,則可在使用注解時直接括號中對value賦值,而不用顯式指定value = RetentionPolicy.CLASS
(4)、Target
指示注解類型所適用的程序元素的種類。如果注解類型聲明中不存在 Target 元注解,則聲明的類型可以用在任一程序元素上。如果存在這樣的元注解,則編譯器強制實施指定的使用限制。 例如,此元注解指示該聲明類型是其自身,即元注解類型。它只能用在注解類型聲明上:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }
要想聲明只能用於某個注解的成員類型使用的注解,則:
@Target({})
ElementType 常量在 Target 注解中至多只能出現一次,如下是非法的:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})
value數組類型為ElementType,同樣是本包的一個枚舉類型,他包含的值較多,參考如下:ElementType.
ANNOTATION_TYPE | 注解類型聲明 |
CONSTRUCTOR | 構造方法聲明 |
FIELD | 字段聲明(包括枚舉常量) |
LOCAL_VARIABLE | 局部變量聲明 |
METHOD | 方法聲明 |
PACKAGE | 包聲明 |
PARAMETER | 參數聲明 |
TYPE | 類、接口(包括注解類型)或枚舉聲明 |
(5)、Repeatable 可重復注解的注解
允許在同一申明類型(類,屬性,或方法)的多次使用同一個注解。
在這個注解出現前,一個位置要想注兩個相同的注解,是不可能的,編譯會出錯誤。所以要想使一個注解可以被注入兩次,需要聲明一個高級注解,這個注解中的成員類型為需要多次注入的注解的注解數組,如:
public @interface Authority { String role(); } public @interface Authorities { Authority[] value(); } public class RepeatAnnotationUseOldVersion { @Authorities({@Authority(role="Admin"),@Authority(role="Manager")}) public void doSomeThing(){ } }
由另一個注解來存儲重復注解,在使用時候,用存儲注解Authorities來擴展重復注解。這樣可以實現為一個方法注解兩個Authority,但是這樣可讀性比較差。
通過Repeatable可以這樣實現上面的效果:
@Repeatable(Authorities.class) public @interface Authority { String role(); } public @interface Authorities { Authority[] value(); } public class RepeatAnnotationUseNewVersion { @Authority(role="Admin") @Authority(role="Manager") public void doSomeThing(){ } }
在注解Authority上告訴該注解,如果多次用Authority注解了某個方法,則自動把多次注解Authority作為Authorities注解的成員數組的一個值,當取注解時,可以直接取Authorities,即可取到兩個Authority注解。要求:@Repeatable注解的值的注解類Authorities.class,成員變量一定是被注解的注解Authority的數組。
不同的地方是,創建重復注解Authority時,加上@Repeatable,指向存儲注解Authorities,在使用時候,直接可以重復使用Authority注解。從上面例子看出,java 8里面做法更適合常規的思維,可讀性強一點
其實和第一種是一模一樣的,只是增加了可讀性。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Repeatable { /** * Indicates the <em>containing annotation type</em> for the * repeatable annotation type. * @return the containing annotation type */ Class<? extends Annotation> value(); }
(6)、Native
Indicates that a field defining a constant value may be referenced from native code. The annotation may be used as a hint by tools that generate native header files to determine whether a header file is required, and if so, what declarations it should contain.
僅僅用來標記native的屬性
@Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) public @interface Native { }
只對屬性有效,且只在代碼中使用,一般用於給IDE工具做提示用。
2、編寫自己的注解:注解接口 Annotation
所有注解默認都實現了這個接口,實現是由編譯器完成的,編寫自己的接口的方法:
使用@interface自定義注解時,自動繼承了java.lang.annotation.Annotation接口,由編譯程序自動完成其他細節。在定義注解時,不能繼承其他的注解或接口。@interface用來聲明一個注解,其中的每一個方法實際上是聲明了一個配置參數。方法的名稱就是參數的名稱,返回值類型就是參數的類型(返回值類型只能是基本類型、Class、String、enum)。可以通過default來聲明參數的默認值。同時value屬性是一個注解的默認屬性,只有value屬性時是可以不顯示賦值的。
定義注解格式:
public @interface 注解名 {定義體}
使用注解格式:
@注解名(key=value, key=value)
注解參數的可支持數據類型:
1.所有基本數據類型(int,float,boolean,byte,double,char,long,short)
2.String類型
3.Class類型
4.enum類型
5.Annotation類型
6.以上所有類型的數組
Annotation類型里面的參數該怎么設定:
第一,只能用public或默認(default)這兩個訪問權修飾.例如,String value();這里把方法設為defaul默認類型;
第二,參數成員只能用基本類型byte,short,char,int,long,float,double,boolean八種基本數據類型和 String,Enum,Class,Annotation等數據類型,以及這一些類型的數組.例如,String value();這里的參數成員就為String;數組類型類似於String[] value();
第三,如果只有一個參數成員,最好把參數名稱設為"value",后加小括號.或者只有一個參數沒有默認值,其他都有,也可以把這個參數名稱設為"value",這樣使用注解時就不用顯式聲明屬性了。
第四,如果一個參數成員類型為數組,如果 String[] array();傳值方式為array={"a","b"},若只有一個值,則可以直接令array="a",會自動生成一個只包含a的數組。若沒有值,則array={}。都是可以的。
注解元素的默認值:
注解元素必須有確定的值,要么在定義注解的默認值中指定,要么在使用注解時指定,非基本類型的注解元素的值不可為null。因此, 使用空字符串或0作為默認值是一種常用的做法。這個約束使得處理器很難表現一個元素的存在或缺失的狀態,因為每個注解的聲明中,所有元素都存在,並且都具有相應的值,為了繞開這個約束,我們只能定義一些特殊的值,例如空字符串或者負數,一次表示某個元素不存在,在定義注解時,這已經成為一個習慣用法。
Annotation接口中方法:
Class<? extends Annotation> annotationType() 返回此 annotation 的注解類型。
boolean equals(Object obj) 如果指定的對象表示在邏輯上等效於此接口的注解,則返回 true。
String toString() 返回此 annotation 的字符串表示形式。
所有Annotation類中的Class<?> getClass()。
3、通過反射獲取Annotation類對象
注解對象是在一個類的class對象中的,一個類只有一個class實例,所以Annotation也是唯一的,對應於一個class文件。注:一個Class對象實際上表示的是一個類型,而這個類型未必一定是一種類。例如,int不是類,但int.class是一個Class類型的對象。虛擬機為每個類型管理一個Class對象。因此,可以用==運算符實現兩個類對象比較的操作。
如果沒有用來讀取注解的方法和工作,那么注解也就不會比注釋更有用處了。使用注解的過程中,很重要的一部分就是創建於使用注解處理器。Java SE5擴展了反射機制的API,以幫助程序員快速的構造自定義注解處理器。
反射中獲取注解的類庫,注解處理器類庫(java.lang.reflect.AnnotatedElement):
Java使用Annotation接口來代表程序元素前面的注解,該接口是所有Annotation類型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受注解的程序元素,該接口主要有如下幾個實現類:
類 | 說明 | 對應的ElementType |
Class | 類定義 | TYPE、ANNOTATION_TYPE |
Constructor | 構造器定義 | CONSTRUCTOR |
Field | 類的成員變量定義 | FIELD |
Method | 類的方法定義 | METHOD |
Package | 類的包定義 | PACKAGE |
注1:TYPE其實已經包含了ANNOTATION_TYPE,這個只是為了更細分
注2:上面沒有提到的ElementType.PARAMETER,可以使用Method類的Annotation[][] getParameterAnnotations() 方法獲取,多個參數每個參數都可能有多個注解,所以才是二維數組。
注3:LOCAL_VARIABLE暫時不知道怎么獲取,好像也沒啥必要獲取。
方法使用:AnnotatedElement接口中有四個方法,用於獲取注解類型
<T extends Annotation> T getAnnotation(Class<T> annotationClass) 如果存在該元素的指定類型的注釋,則返回這些注釋,否則返回 null。
Annotation[] getAnnotations() 返回此元素上存在的所有注釋。
Annotation[] getDeclaredAnnotations() 返回直接存在於此元素上的所有注釋。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 如果指定類型的注釋存在於此元素上,則返回 true,否則返回 false。
用法:注解類型 anno = Class.getAnnotation(注解類型.class)
之后就可以調用注解類型中的屬性來獲取屬性值了。示例:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Father { String value(); } @Father("bca") public class Son { public static void main(String[] args) { Father father = Son.class.getAnnotation(Father.class); System.out.println(father.value()); } }
Java8中又補充了三個方法,用於對@Repeatable進行支持:
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) 返回直接存在於此元素上的指定類型的注釋。忽略繼承的注解。
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) 返回重復注解的類型,被同注解注解的元素返回該類型注解的數組。
default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> anotationClass) 返回重復注解的類型,被同注解注解的元素返回注解的數組。忽略繼承的注解。
import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; public class RepeatingAnnotations { @Target( ElementType.TYPE ) @Retention( RetentionPolicy.RUNTIME ) public @interface Filters { Filter[] value(); } @Target( ElementType.TYPE ) @Retention( RetentionPolicy.RUNTIME ) @Repeatable( Filters.class ) public @interface Filter { String value(); }; @Filter( "filter1" ) @Filter( "filter2" ) public interface Filterable { } public static void main(String[] args) { for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) { System.out.println( filter.value() ); } } }
注意:Annotation是一個特殊的class,類似於enum,由於與普通class的特異性,使用getAnnocation獲取的返回值,其實Annotation的代理類:sun.reflect.annotation.AnnotationInvocationHandle,所有對注解內屬性的訪問都是通過代理類實現的。關於代理請看后面文章。
http://www.2cto.com/kf/201502/376988.html