聊聊Java的註解及實現

前言

Annotation(註解)就是Java提供了一種元程序中的元素關聯任何信息和着任何元數據(metadata)的途徑和方法。Annotion(註解)是一個接口,程序能夠經過反射來獲取指定程序元素的Annotion對象,而後經過Annotion對象來獲取註解裏面的元數據。Annotation(註解)是JDK5.0及之後版本引入的。它能夠用於建立文檔,跟蹤代碼中的依賴性,甚至執行基本編譯時檢查。從某些方面看,annotation就像修飾符同樣被使用,並應用於包、類 型、構造方法、方法、成員變量、參數、本地變量的聲明中。這些信息被存儲在Annotation的「name=value」結構對中。html

註解基礎

 Annotation的成員在Annotation類型中以無參數的方法的形式被聲明。其方法名和返回值定義了該成員的名字和類型。在此有一個特定的默認語法:容許聲明任何Annotation成員的默認值:一個Annotation能夠將name=value對做爲沒有定義默認值的Annotation成員的值,固然也可使用name=value對來覆蓋其它成員默認值。這一點有些近似類的繼承特性,父類的構造函數能夠做爲子類的默認構造函數,可是也能夠被子類覆蓋。java

  Annotation能被用來爲某個程序元素(類、方法、成員變量等)關聯任何的信息。須要注意的是,這裏存在着一個基本的規則:Annotation不能影響程序代碼的執行,不管增長、刪除 Annotation,代碼都始終如一的執行。另外,儘管一些annotation經過java的反射api方法在運行時被訪問,而java語言解釋器在工做時忽略了這些annotation。正是因爲java虛擬機忽略了Annotation,致使了annotation類型在代碼中是「不起做用」的; 只有經過某種配套的工具纔會對annotation類型中的信息進行訪問和處理。本文中將涵蓋標準的Annotation和meta-annotation類型,陪伴這些annotation類型的工具是java編譯器(固然要以某種特殊的方式處理它們)。編程

元數據從metadata一詞譯來,就是「關於數據的數據」的意思。
  元數據的功能做用有不少,好比:你可能用過Javadoc的註釋自動生成文檔。這就是元數據功能的一種。總的來講,元數據能夠用來建立文檔,跟蹤代碼的依賴性,執行編譯時格式檢查,代替已有的配置文件。若是要對於元數據的做用進行分類,目前尚未明確的定義,不過咱們能夠根據它所起的做用,大體可分爲三類:api

  1. 編寫文檔:經過代碼裏標識的元數據生成文檔數組

  2. 代碼分析:經過代碼裏標識的元數據對代碼進行分析安全

  3. 編譯檢查:經過代碼裏標識的元數據讓編譯器能實現基本的編譯檢查
      架構

在Java中元數據以標籤的形式存在於Java代碼中,元數據標籤的存在並不影響程序代碼的編譯和執行,它只是被用來生成其它的文件或針在運行時知道被運行代碼的描述信息。
綜上所述:
    jvm

  • 元數據以標籤的形式存在於Java代碼中。ide

  • 元數據描述的信息是類型安全的,即元數據內部的字段都是有明確類型的。函數

  • 元數據須要編譯器以外的工具額外的處理用來生成其它的程序部件。

  • 元數據能夠只存在於Java源代碼級別,也能夠存在於編譯以後的Class文件內部。

經常使用註解

根據註解參數的個數,咱們能夠將註解分爲三類:

  • 標記註解:一個沒有成員定義的Annotation類型被稱爲標記註解。這種Annotation類型僅使用自身的存在與否來爲咱們提供信息。好比後面的系統註解@Override;

  • 單值註解

  • 完整註解  

根據註解使用方法和用途,咱們能夠將Annotation分爲三類:

  • JDK內置系統註解

  • 元註解

  • 自定義註解

Java註解架構圖

自定義註解

在這裏就只給個例子,請務必查閱參考文章的第二篇

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
    /**
     * 顏色枚舉
     * @author peida
     *
     */
    public enum Color{ BULE,RED,GREEN};
    
    /**
     * 顏色屬性
     * @return
     */
    Color fruitColor() default Color.GREEN;

}

順便再強調幾個重要的點:

  • 註解元素必須有肯定的值,要麼在定義註解的默認值中指定,要麼在使用註解時指定,非基本類型的註解元素的值不可爲null。所以, 使用空字符串或0做爲默認值是一種經常使用的作法。這個約束使得處理器很難表現一個元素的存在或缺失的狀態,由於每一個註解的聲明中,全部元素都存在,而且都具備相應的值,爲了繞開這個約束,咱們只能定義一些特殊的值,例如空字符串或者負數,一次表示某個元素不存在,在定義註解時,這已經成爲一個習慣用法。

  • Annotation類型裏面的參數只能用public或默認(default)這兩個訪問權修飾.例如,String value();這裏把方法設爲defaul默認類型。

  • 參數成員只能用基本類型byte,short,char,int,long,float,double,boolean八種基本數據類型和 String,Enum,Class,annotations等數據類型,以及這一些類型的數組.例如,String value();這裏的參數成員就爲String。

  • 若是隻有一個參數成員,最好把參數名稱設爲"value",後加小括號.例:下面的例子FruitName註解就只有一個參數成員。

處理註解

  Java使用Annotation接口來表明程序元素前面的註解,該接口是全部Annotation類型的父接口。除此以外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口表明程序中能夠接受註解的程序元素,該接口主要有以下幾個實現類:

  • Class:類定義

  • Constructor:構造器定義

  • Field:累的成員變量定義

  • Method:類的方法定義

  • Package:類的包定義

java.lang.reflect 包下主要包含一些實現反射功能的工具類,實際上,java.lang.reflect 包全部提供的反射API擴充了讀取運行時Annotation信息的能力。當一個Annotation類型被定義爲運行時的Annotation後,該註解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation纔會被虛擬機讀取。

AnnotatedElement 接口是全部程序元素(Class、Method和Constructor)的父接口,因此程序經過反射獲取了某個類的AnnotatedElement對象以後,程序就能夠調用該對象的以下四個個方法來訪問Annotation信息:

  • 方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定類型的註解,若是該類型註解不存在,則返回null。

  • 方法2:Annotation[] getAnnotations():返回該程序元素上存在的全部註解。

  • 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的註解,存在則返回true,不然返回false.

  • 方法4:Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的全部註釋。與此接口中的其餘方法不一樣,該方法將忽略繼承的註釋。(若是沒有註釋直接存在於此元素上,則返回長度爲零的一個數組。)該方法的調用者能夠隨意修改返回的數組;這不會對其餘調用者返回的數組產生任何影響。

下面給一個處理的例子

@Target(value = { ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAnnotation {
    public int id() default 0;
    public String name() default "";
    public int age() default 18;
    public String gender() default "M";
}

public class TestMain
{
  @UserAnnotation(age=20,gender="F",id=2014,name="zhangsan")//註解的使用
  private Object obj;
  
  public static void main(String[] args) throws Exception
  {
     Filed objField = TestMain.class.getField("obj");
     UserAnnotation ua = objField.getAnnotation(UserAnnotation.class);//獲得註解,起到了標記的做用
     
    System.out.println(ua.age()+","+ua.gender()+","+ua.id()+","+ua.name());
    //***進一步操做的話,假設Object要指向一個User類,那麼能夠講註解的值給他
    TestMain tm = new TestMain();
    objFiled.set(tm,new User(ua.age(),ua.gender(),ua.id(),ua.name())); //不錯吧,將本身的信息送給obj,起到了附加信息的做用
    
    //-----------請自由遐想吧~~,下面來講說註解怎麼能得到註解本身的註解-------------
   Target t = ua.annotationType().getAnnotation(Target.class)
   ElementType[] values = t.value();
   //~~~~~~~~~~~~~~完了,再一次自由遐想吧~~~~~~~~~~~~~~
   
   Sysout.out.println("注意:是遐想,不是瞎想!!");
  }
}

註解實現原理

前面介紹瞭如何使用Java內置的註解以及如何自定義一個註解,接下去看看註解實現的原理,看看在Java的大致系下面是如何對註解的支持的。仍是回到上面自定義註解的例子,對於註解Test,以下,若是對AnnotationTest類進行註解,則運行時能夠經過AnnotationTest.class.getAnnotation(Test.class)獲取註解聲明的值,從上面的句子就能夠看出,它是從class結構中獲取出Test註解的,因此確定是在某個時候註解被加入到class結構中去了。

@Test("test")
public class AnnotationTest {
    public void test(){
    }
}

從java源碼到class字節碼是由編譯器完成的,編譯器會對java源碼進行解析並生成class文件,而註解也是在編譯時由編譯器進行處理,編譯器會對註解符號處理並附加到class結構中,根據jvm規範,class文件結構是嚴格有序的格式,惟一能夠附加信息到class結構中的方式就是保存到class結構的attributes屬性中。咱們知道對於類、字段、方法,在class結構中都有本身特定的表結構,並且各自都有本身的屬性,而對於註解,做用的範圍也能夠不一樣,能夠做用在類上,也能夠做用在字段或方法上,這時編譯器會對應將註解信息存放到類、字段、方法本身的屬性上。

在咱們的AnnotationTest類被編譯後,在對應的AnnotationTest.class文件中會包含一個RuntimeVisibleAnnotations屬性,因爲這個註解是做用在類上,因此此屬性被添加到類的屬性集上。即Test註解的鍵值對value=test會被記錄起來。而當JVM加載AnnotationTest.class文件字節碼時,就會將RuntimeVisibleAnnotations屬性值保存到AnnotationTest的Class對象中,因而就能夠經過AnnotationTest.class.getAnnotation(Test.class)獲取到Test註解對象,進而再經過Test註解對象獲取到Test裏面的屬性值。

這裏可能會有疑問,Test註解對象是什麼?其實註解被編譯後的本質就是一個繼承Annotation接口的接口,因此@Test其實就是「public interface Test extends Annotation」,當咱們經過AnnotationTest.class.getAnnotation(Test.class)調用時,JDK會經過動態代理生成一個實現了Test接口的對象,並把將RuntimeVisibleAnnotations屬性值設置進此對象中,此對象即爲Test註解對象,經過它的value()方法就能夠獲取到註解值。

Java註解實現機制的整個過程如上面所示,它的實現須要編譯器和JVM一塊兒配合。

參考文章

相關文章
相關標籤/搜索