java基礎-注解Annotation原理和用法
在很多java代碼中都可以看到諸如@Override、@Deprecated、@SuppressWarnings這樣的字符,這些就是注解Annotation。注解最早在jdk5中被引入,現在已經成為java平臺很重要的一部分了,很多的框架程序中也喜歡使用注解,如Spring、Mybatis等。
那么,什么是注解呢?注解就是元數據,一種描述數據的數據,通俗一點就是為程序的元素(類、方法、成員變量)加上更直觀的說明,這些說明信息是與程序的業務邏輯無關的。但是,我們可以通過java的反射機制來獲取Annotation的信息,并根據這些信息來對程序進行賦值、分發等操作。
java5.0定義了4個標準的meta-annotation元注解,它們被用來提供對其它annotation類型作說明,四種元注解如下:
- @Target;
- @Retention;
- @Inherited;
- @Documented;
下面詳細說明這四種元注解的作用:
@Target
被用于描述注解的使用范圍,即注解可以用在所修飾對象的什么地方,取值可以是ElementType中的一種:
- CONSTRUCTOR:用于描述構造器;
- FIELD:用于描述域;
- LOCAL_VARIABLE:用于描述局部變量;
- METHOD:用于描述方法;
- PACKAGE:用于描述包;
- PARAMETER:用于描述參數;
- TYPE:用于描述類、接口、注解類型或枚舉;
@Target(ElementType.TYPE) public @interface Exculde{ /** * 名稱,默認值為"" * @return */ public String name() default ""; } @Target(ElementType.FIELD) public @interface Inject{ /** * id,默認值為"" * @return */ public String id() default ""; /** * 類,默認值為"" * @return */ public Class clazz() default Object.class; }
上面定義了兩個注解,注解@Exculde只能用于修飾類、接口、注解、枚舉,注解@Inject只能修飾類型的域,如:
@Exculde(name="admin") public class User{ @Inject(id="username",clazz=String.class) private String username; }
@Retention
用于描述注解的生命周期,即注解能在源碼到JVM裝載過程中的哪一個級別上有效。有些annotation僅出現在源碼中,被編譯器丟棄;有些annotation能被編譯進class文件中,可能被JVM忽略;有些annotation不但能夠被編譯進class文件,而且能夠在class文件被裝載時被讀取。這三種情況對應RetentionPoicy的三種取值:
- SOURCE:源碼文件中保留;
- CLASS:class文件中保留;
- RUNTIME:運行時保留;
@Inherited
用于描述注解是可以被繼承的,如果一個使用了@Inherited修飾的annotation被用于一個class,那么這個annotation也將被用于這個class的子類。@Inherited是一個標記注解,沒有參數選項,它修飾的annotation是被標記的class的子類所繼承,類并不從它所實現的接口繼承annotation,方法并不從它所重載的方法繼承annotation。
當使用java的反射去獲取一個@Inherited修飾的annotation時,反射檢查將遞歸檢查,檢查class和其父類,直到發現指定的annotation類型被發現,或者到達類繼承結構的頂層。
@Documented
用于描述注解信息應該被作為被標注的程序的公共API,即應該把注解信息保留文檔中。@Documented也是一個標記注解,沒有參數選項。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Exculde{ /** * 名稱注解,默認值為"" * @return */ public String name() default ""; }
如果你看過了上面的元注解,如果還不能理解也沒關系,下面我們通過自定義注解來進一步理解元注解。
自定義注解需要使用@interface,類似于定義一個類使用class,但定義注解時不能再繼承其它的類或者接口,它已經自動繼承了java.lang.annotation.Annotation接口。@interface用來聲明一個注解,其中的每一個方法實際上聲明了一個配置參數,方法的名稱就是參數的名稱,方法的返回值類型就是參數的類型,也可以使用default來聲明參數的默認值。
定義注解的格式如下:
public @interface 注解名{ 定義體 }
注解參數可支持的數據類型如下:
- 基本類型;
- Class;
- String;
- Enum;
- Annotation;
- 以上所有類型的數組形式;
下面定義一個類似于Spring的注解,用于向實例對象注入屬性的值:
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Inject{ /** * ID,默認值為"" * @return */ public String id() default ""; /** * 類,默認值為"" * @return */ public Class clazz() default Object.class; }
把@Inject標注在類UserAction的屬性上:
public class UserAction { @Inject(id="userService",clazz=UserService.class) private UserService userService; }
在IOC容器框架中,對象都會被自動初始化,如果我們要實現IOC的這種功能,我們應該為加上@Inject注解的屬性userService注入它的值。首先我們應該通過反射獲取userService的域對象field,通過field獲取@Inject注解的信息,然后根據注解的id和clazz得到它依賴的值:
Inject inject = field.getAnnotation(Inject.class); String id = inject.id(); Class clazz = inject.clazz(); Object userService = Class.forName(clazz.getName).newInstance(); field.setAccessible(true); field.set(object,userService);
上面代碼中,調用field.getAnnotation(Inject.class)獲取到@Inject的對象,然后獲取@Inject的id和clazz值,通過反射實例化clazz的對象,再反射賦值給field。這就是Spring那些框架的依賴注入的實現原理,有興趣的可以自己再優化一下。
讀取類的注解信息還有其它的幾個方法,在此不再一一說明,可以自行研究java.lang.reflect包。經過上面的說明,由此我們也可以知道注解僅僅是一種元數據,增強類、屬性、參數的描述,使用注解的關鍵在于獲取注解的信息,再通過反射的手段來實現注解想達成的功能。
文章同步發布在朗度云網站,傳送門:
----------------------------------------------------------------------
“我可以接受失敗,但絕對不能接受自己未曾奮斗過。”--邁克爾-喬丹
“I can accept failure, but I can't accept not trying.”--Michael Jordan.

浙公網安備 33010602011771號