java註解細節
現在很多框架都使用註解了,典型的像Spring裏面就可以看到大量的註解,比如@Service,@Controller這一類的都是註解。註解Annotation是java一項很重要的功能。下面就來整理一下關於註解的一些細節。
1.首先,什麽是註解呢?
較為官方的解釋是,註解是元數據,就是解釋數據的數據。說得通俗一點,它是一種能夠修飾類、變量、方法、參數等數據的元數據。以一個簡單的例子,我們經常看到的一個註解是@Override
。比如如下代碼。
package com.xdx.learn; public class Father { public void func(){ System.out.println("hello"); } } class son extends Father{ @Override public void func() { System.out.println("hi"); } }
可以看到在son類中,重寫了(覆蓋)父類的func()方法,我們用了一個@Override的註解去修飾這個方法。它起到的作用是告訴編譯器,這個方法時覆蓋父類的方法,假如我不小心把func寫成了Func,編譯器由於已經得知了我是想覆蓋父類的func方法,就會提示我,父類並沒有一個叫做Func的方法,我就會去檢查,哦,原來是我寫錯了啊。
所以註解起到了一種修飾元數據的作用,被修飾過的元數據具有某些特性,這些特性將在程序編譯、運行等時段被利用。具體是如何利用而起作用的,我們就不用去管了。提供該註解的提供者會搞定。
比如Spring中用@Resource註解的成員變量,將在該類實例化的時候,被註入到類的實例當中。而具體如何利用@Resource註解實現這一過程的,Spring幫我們做了。
2.註解的定義
我們來看看上面講的@Resource
的註解的內部是如何定義的。
@Target({TYPE, FIELD, METHOD}) @Retention(RUNTIME) public @interface Resource { String name() default ""; String lookup() default ""; Class<?> type() defaultjava.lang.Object.class; enum AuthenticationType { CONTAINER, APPLICATION } AuthenticationType authenticationType() default AuthenticationType.CONTAINER; boolean shareable() default true; String mappedName() default ""; String description() default ""; }
上述便是一個註解的定義,可以看到,這個註解都被兩個元註解所修飾,分別是@Target和@Retention
先來看看@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和@Retention,還多出了一個@Documented。
事實上,總共有4種元註解,@Target、@Retention、@Documented、@Inherited。
@Target——指明該類型的註解可以註解的程序元素的範圍(作用目標)。該元註解的取值可以為TYPE,METHOD,CONSTRUCTOR,FIELD等。如果Target元註解沒有出現,那麽定義的註解可以應用於程序的任何元素。由上述的@interface Target這個註解的定義,我們可以看到它的取值是一個枚舉類型的數組。事實上,它們具體的值如下。
public enum ElementType { /** Class, interface (including annotation type), or enum declaration */ TYPE, /** Field declaration (includes enum constants) */ FIELD, /** Method declaration */ METHOD, /** Formal parameter declaration */ PARAMETER, /** Constructor declaration */ CONSTRUCTOR, /** Local variable declaration */ LOCAL_VARIABLE, /** Annotation type declaration */ ANNOTATION_TYPE, /** Package declaration */ PACKAGE, /** * Type parameter declaration * * @since 1.8 */ TYPE_PARAMETER, /** * Use of a type * * @since 1.8 */ TYPE_USE }
@Documented --類和方法的Annotation在缺省情況下是不出現在javadoc中的。如果使用@Documented修飾該Annotation,則表示它可以出現在javadoc中。
定義Annotation時,@Documented可有可無;若沒有定義,則Annotation不會出現在javadoc中。
@Retention---指定註解的生命周期,也就是它能存活到什麽時候,我們來看看@Retention的具體定義就知道。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); }
然後我們看看RetentionPolicy 這個枚舉類有哪些對象。
public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. */ SOURCE, /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. */ CLASS, /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement */ RUNTIME }
很清楚了,註解可以存活的生命周期有3種,編譯階段,存儲在.class文件裏面,還要就是到運行階段。這裏需要註意的是,只有當註解的Retention是RetentionPolicy.RUNTIME的時候,我們才可以通過反射獲取方法或者類的註解信息。因為反射機制是在類加載完成以後的運行階段發揮作用的。
@Inherited——如果一個類的註解(比如@A)被@Inherited元註解所標註,則這個類的子類可以繼承該類的這個註解@A。註意的是,這種繼承是針對類的註解,如果是方法的註解,則不需要用@Inherited。子類在繼承父類的方法的同時也繼承了這個方法的註解,除非你去重寫方法。
回到@Resource註解,我們看到,定義一個註解需要用public @interface來修飾,並且被@Target、@Retention、@Documented(可選)、@Inherited(可選)這四個元註解所修飾。我們可以把註解看成一種特殊的類。看類內部,定義了很多方法,沒有訪問修飾符,沒有方法體,沒有參數,只有返回值類型。返回值可以帶有默認值,如
String name() default "";
String lookup() default "";
Class<?> type() default java.lang.Object.class;
enum AuthenticationType { CONTAINER, APPLICATION }
AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
boolean shareable() default true;
String mappedName() default ""; String description() default "";
需要記住的是這種方法定義的格式,無參,無方法體,事實上,我們可以理解成定義了一些成員變量,只不過這些成員變量名後面都加了一個(),並且可用default關鍵字來賦初值。
定義了成員變量以後,我們可以在用這個註解的時候給成員變量賦值。比如@Resource(name="baseDao")。
ps:如果註解中只有一個成員,且叫做value,我們在傳入value的值的時候,就不用指定這個成員的名稱的了。比如@Target(ElementType.ANNOTATION_TYPE)就屬於這種情況。
3.就從一個註解類本身提供的信息而言,我們只能知道它的作用目標(從@Target元註解獲悉),生命周期(@Retention元註解獲悉),然後還能知道的是它的成員變量。除了從其他渠道(比如看源碼註釋)獲悉這個註解的具體作用並加以應用(比如使用@Service標註一個Service類),僅從註解的定義文件你是看不出它有什麽作用的。而我們對註解信息的編程也很有限,可以通過反射機制獲取類,成員,或者方法的註解信息,註解的成員值等。比如下面代碼。
package com.xdx.learn; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //定義一個註解類,可作用於類和方法 @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.METHOD}) public @interface Anno { String value() default ""; } package com.xdx.learn; import java.lang.reflect.Method; @Anno("anno") public class AnnoTest { @Anno("anno") public void func(){ System.out.println("hello"); } public static void main(String args[]) throws ClassNotFoundException, NoSuchMethodException, SecurityException{ Class clz=Class.forName("com.xdx.learn.AnnoTest"); //反射獲取類的註解信息 if(clz.getAnnotation(Anno.class)!=null){ Anno anno=(Anno) clz.getAnnotation(Anno.class); System.out.println(anno.value()); System.out.println(anno.annotationType()); } //反射獲取方法的註解信息 Method method=clz.getDeclaredMethod("func", null); if(method.isAnnotationPresent(Anno.class)){ Anno anno=method.getAnnotation(Anno.class); System.out.println(anno.value()); System.out.println(anno.annotationType()); } } }
上述代碼運行的結果為:
anno
interface com.xdx.learn.Anno
anno
interface com.xdx.learn.Anno
java註解細節