1. 程式人生 > >Java 之註解(Annotation)

Java 之註解(Annotation)

 

1.Annotation為何而來

What:Annotation幹嘛的

  • JDK5開始,java增加了對元資料(MetaData)的支援,怎麼支援?答:通過Annotation(註解)來實現。Annotation提供了為程式元素設定元資料的方法。元資料:描述資料的資料。

  • Annotation可以為哪些程式元素設定元資料呢? Annotation提供了一種為程式元素設定元資料的方法,包括修飾包、類、構造器、方法、成員變數、引數、區域性變數的宣告。元資料的資訊被儲存在Annotation的“name=value”對中。

  • Annotation怎麼實現設定元資料?程式如何讀取這些元資料?

    答:元資料的資訊被儲存在Annotation的“name=value”對中。Annotation是一個介面,程式可以通過反射來獲取指定程式元素的Annotation物件,然後通過Annotation物件來取得註解裡的元資料。

  • Annotation不影響程式程式碼的執行,無論增加、刪除Annotation,程式碼都始終如一的執行。如果希望讓程式中的Annotation在執行時起一定的作用,只有通過某種配套工具對Annotation中的資訊進行訪問和處理。jdk7之前訪問和處理Annotation的工具統稱APT(Annotation Processing Tool)(jdk7後就被廢除了),jdk7及之後採用了JSR 269 API。相關原因

    官方說明原因

  • 結論:java想給程式元素提供元資料支援,於是創造了Annotation來實現這個目標。

註解的使用案例

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
}

Why:為什麼要提供元資料支援

  • 通過Annotation設定的元資料在什麼時候被讀取?讀取能幹嘛?答:Annotation就像程式碼裡的特殊標記,這些標記可以在編譯、類載入、執行時被讀取。讀取到了程式元素的元資料,就可以執行相應的處理。通過註解,程式開發人員可以在不改變原有邏輯的情況下,在原始碼檔案中嵌入一些補充資訊。程式碼分析工具、開發工具和部署工具可以通過解析這些註解獲取到這些補充資訊,從而進行驗證或者進行部署等。

  • 比如:上面程式碼,讀取到 id變數上面有@GeneratedValue(strategy=GenerationType.AUTO)註解,並且註解提供了strategy=GenerationType.AUTO這樣的元資料資訊,那麼程式就會為id設定一個自增的值。讀取到Book類上面有一個@Entity註解,程式就會認為這是一個持久化類,就會做一些持久化的處理。

  • 不使用Annotation怎麼為程式元素提供元資料
    看來元資料在程式設計中還是能起到很大的作用的,如果沒有元資料還真的不好辦,比如上面程式碼中id成員變數的元資料是“strategy=GenerationType.AUTO即採用自增策略”,如果沒有這個元資料支援,程式中怎麼才能為id賦一個自增的值呢?憂愁。

  • 提供元資料只有通過Annotation才可以嗎?答:不是,通過配置檔案也可以。比如還是上面程式碼id這個變數,我現在想為它新增描述資料即元資料,內容是:採用自增策略。這個資訊通過Annotation來實現就是上面程式碼的樣子。通過配置檔案實現的話,比如採用xml格式配置檔案。那麼我可以在檔案中配置<property-MetaData class="Book " property="id" metadata="auto">。哈哈!比如我就定一個規則:class表示類,property表示類的某個屬性,metadata是屬性的元資料。程式在啟動時通過讀取這個檔案的資訊就可以知道id變數的元資料了,知道元資料就可以做相應處理了。當然,通過配置檔案還是沒有註解方便。


知道元資料在程式設計中的重要性和提供元資料的方法Annotation了,那麼就來學習Annotation吧。

  • 提示:有些註解只是為了防止我們犯低階錯誤,通過這些註解,讓編譯器在編譯期就可以檢查出一些低階錯誤,對於這些註解,可以加或者不加,當然還有很多其他註解都是起輔助程式設計作用。但是有一些註解的作用很重要,不加的話就實現不了一些功能,比如,資料持久化操作中,通過@Entity註解來標識持久化實體類,如果不使用該註解程式就識別不了持久化實體類。

2.基本Annotation

  • Java提供了5個基本的Annotation的用法,在使用Annotation時要在其前面增加@符號。

  • @Override :限定重寫父類方法

  • @Deprecated:表示已過時

  • @SuppressWarnings:抑制編譯警告

  • @SafeVarargs (java7新增):去除“堆汙染”警告

  • @Functionlnterface (java8新增):修飾函式式介面

  • @Override :用來指定方法覆載的,它可以強制一個子類必須覆蓋父類的方法。寫在子類的方法上,在編譯期,編譯器檢查這個方法,保證父類包含被該方法重寫的方法,否則編譯出錯。該註解只能修飾方法,在編譯期被讀取。

  • @Deprecated:用於表示某個程式元素(類、方法等)已過時。編譯時讀取,編譯器編譯到過時元素會給出警告。

  • @SuppressWarnings:抑制編譯警告,被該註解修飾的程式元素(以及該程式元素中的所有子元素)取消顯示指定的編譯警告。
    比如:取消如果程式使用沒有泛型限制的集合會引起編譯器警告,為了避免這種警告使用該註解。

    • unchecked異常:執行時異常。是RuntimeException的子類,不需要在程式碼中顯式地捕獲unchecked異常做處理。Java異常
@SuppressWarnings(value="unchecked")
public class SuppressWarningTest{
   public static void main(String[] args)
   {
       List<String> myList = new ArrayList();
   }
}
@SuppressWarnings("deprecation")   //取消過時警告
    public HibernateTemplate getHt() {
        return ht;
    }
  • @SafeVarargs (java7新增):java7的“堆汙染”警告與@SafeVarargs
    堆汙染:把一個不帶泛型的物件賦給一個帶泛型的變數是,就會發生堆汙染。
    例如:下面程式碼引起堆汙染,會給出警告
List l2 = new ArrayList<Number>();
List<String> ls = l2;    
  • 3中方式去掉這個警告

    • 使用註解@SafeVarargs修飾引發該警告的方法或構造器。
    • 使用@SuppressWarnings("unchecked") 修飾。
    • 使用編譯器引數命令:-Xlint:varargs
  • @Functionlnterface (java8新增):修飾函式式介面
    使用該註解修飾的介面必須是函式式介面,不然編譯會出錯。那麼什麼是函式式介面?答:如果介面中只有一個抽象方法(可以包含多個預設方法或static方法),就是函式式介面。
    如:

@Functionlnterface
public interface FunInterface{
  static void foo(){
   System.out.println("foo類方法");
  }
  default void bar(){
   System.out.println("bar預設方法");
  }
  void test();//只定義一個抽象方法,預設public
}

3.JDK的元Annotation

  • 元註解(Meta Annotation):和元資料一樣,修飾註解的註解。
  • java提供了6個元註解(Meta Annotation),在java.lang.annotation中。其中5個用於修飾其他的Annonation定義。而@Repeatable專門用於定義Java8新增的重複註解。所以要定義註解必須使用到5個元註解來定義。

@Retention(英文:保留)

  • 用於指定被修飾的Annotation可以保留多長時間,只能修飾Annotation定義。@Retention包含一個RetentionPolicy型別的value成員變數,使用@Retention必須為該value成員變數指定值。value成員變數的值有3個選擇:
  • RetentionPolicy.CLASS:編譯器將把Annotation記錄在class檔案中。當執行java程式時,JVM不可獲取Annotation資訊。(預設值)
  • RetentionPolicy.RUNTIME:編譯器將把Annotation記錄在class檔案中。當執行java程式時,JVM也可獲取Annotation資訊,程式可以通過反射獲取該Annotation資訊
  • RetentionPolicy.SOURCE:Annotation只保留在原始碼中(.java檔案中),編譯器直接丟棄這種Annotation。
    案例:
//定義下面的Testable Annotation保留到執行時,也可以使用value=RetentionPolicy.RUNTIME
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable{}

@Target ( 目標)

用於指定被修飾的Annotation能用於修飾哪些程式單元,只能修飾Annotation定義。它包含一個名為value的成員變數,取值如下:

  • @Target(ElementType.ANNOTATION_TYPE):指定該該策略的Annotation只能修飾Annotation.
  • @Target(ElementType.TYPE) //介面、類、列舉、註解
  • @Target(ElementType.FIELD) //成員變數(欄位、列舉的常量)
  • @Target(ElementType.METHOD) //方法
  • @Target(ElementType.PARAMETER) //方法引數
  • @Target(ElementType.CONSTRUCTOR) //建構函式
  • @Target(ElementType.LOCAL_VARIABLE)//區域性變數
  • @Target(ElementType.PACKAGE) ///修飾包定義
  • @Target(ElementType.TYPE_PARAMETER) //java8新增,後面Type Annotation有介紹
  • @Target(ElementType.TYPE_USE) ///java8新增,後面Type Annotation有介紹
@Target(ElementType.FIELD)
public @interface ActionListenerFor{}

@Documented

  • 用於指定被修飾的Annotation將被javadoc工具提取成文件。即說明該註解將被包含在javadoc中。

@Inherited

  • 用於指定被修飾的Annotation具有繼承性。即子類可以繼承父類中的該註解。---》註解@WW被元註解@Inherited修飾,把@WW新增在類Base上,則Base的所有子類也將預設使用@WW註解。

5.自定義註解

  • 使用@interface關鍵字
  • 註解放在修飾元素的上面

5.1一個簡單的註解

//定義一個簡單的註解Test
public @interface Test{}

預設情況下,Annotation可以修飾任何程式元素:類、介面、方法等。

@Test
public class MyClass{

}

5.2帶成員變數的註解

  • 以無形參的方法形式來宣告Annotation的成員變數,方法名和返回值定義了成員變數名稱和型別。使用default關鍵字設定初始值。沒設定初始值的變數則使用時必須提供,有初始值的變數可以設定也可以不設定。
//定義帶成員變數註解MyTag
@Rentention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTag{
  //定義兩個成員變數,以方法的形式定義
  String name();
  int age() default 32;
}

//使用
public class Test{
  @MyTag(name="liang")
  public void info(){}
}

5.3結論

  • 沒帶成員變數的Annotation被稱為標記,這種註解僅利用自身的存在與否來提供資訊,如@Override等。
  • 包含成員變數的Annotation稱為元資料Annotation,因為他們提供更多元資料。

5.4提取Annotation資訊

  • 使用Annotation修飾了類、方法、成員變數等程式元素之後,這些Annotation不會自己生效,必須由開發者通過API來提取並處理Annotation資訊。

  • Annotation介面是所有註解的父介面。

  • 思路:通過反射獲取Annotation,將Annotation轉換成具體的註解類,在呼叫註解類定義的方法獲取元資料資訊。

獲取Annotation

  • AnnotatedElement介面(java.lang.reflect反射包中)代表程式中可以接受註解的程式元素。即所有可以接受註解的程式元素都會實現該介面。而該介面就提供了獲取Annotation的方法,它的所有實現類也便擁有了這些方法。常見的實現類:

  • Class:類定義。

  • Constructor:構造器定義

  • Field:類的成員變數定義

  • Method:類的方法定義。

  • Package:類的包定義。

  • 由此可見,AnnotatedElement介面的實現類都是一些反射技術設計到的類,所以訪問Annotation資訊也是通過反射技術來實現的。

  • java.lang.reflect包下還包含實現反射功能的工具類,java5開始,java.lang.reflect包提供的反射API增加了讀取允許Annotation的能力。但是,只有定義Annotation時使用了@Rentention(RetentionPolicy.RUNTIME)修飾,該Annotation才會在執行時可見,JVM才會在裝載.class檔案時讀取儲存在class檔案中的Annotation*。

  • AnnotatedElement介面獲取Annotation資訊的方法:

  • <T extends Annotation> T getAnnotation(Class<T> annotationClass):返回修飾該程式元素的指定型別的註解,不存在則返回 null。

  • <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass):返回直接修飾該程式元素的指定型別的註解,不存在則返回 null。 (java8新增)

  • Annotation[] getAnnotations():返回此元素上存在的所有註解。

  • Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的所有註解。

  • boolean isAnnotationPresent (Class< ? extends Annotation> annotationClass):如果指定型別的註解存在於此元素上,則返回 true,否則返回 false。
    java8新增了重複註解功能,所以下面兩個方法在java8之後才有:

  • <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass):返回修飾該程式元素的指定型別的多個註解,不存在則返回 null。

  • <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass):返回直接修飾該程式元素的指定型別的多個註解,不存在則返回 null。

  • 案例

  • 需求:獲取Test類的info方法上的所有註解,並打印出來,如果包含MyTag註解,則再輸出MyTag註解的元資料。

  • 實現:正如我們所知,僅在程式中使用註解是不起任何作用的,必須使用註解處理工具來處理程式中的註解。下面就寫一個註解處理類。處理註解的思路如下:通過反射獲取Test的類描述類Class,然後在獲取其info方法描述類Method,因為Method實現了AnnotatedElement介面,所以呼叫getAnnotations方法獲取所有註解,在遍歷列印。

MyTag註解處理器

public class MyTagAnnotationProcessor {
    public static void process(String className) throws ClassNotFoundException{
        try {
             Class clazz =Class.forName(className);
             Annotation[] aArray= clazz.getMethod("info").getAnnotations();
             for( Annotation an :aArray){
                 System.out.println(an);//列印註解
                 if( an instanceof MyTag){
                     MyTag tag = (MyTag) an;
                     System.out.println("tag.name():"+tag.name());
                     System.out.println("tag.age():"+tag.age());
                 }
             }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

場景測試

public static void main(String[] args) {
        try {
            MyTagAnnotationProcessor.process("annotation.Test");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

測試結果

@annotation.MyTag(age=25, name=liang)
tag.name():liang
tag.age():25

6.使用Annotation示例

  • 想找spring中關於註解定義、使用、註解處理的程式碼,註解處理的程式碼沒找到,不知道在哪個類中。

7.Java8新增的重複註解

  • 在java8以前,同一個程式元素只能使用一個相同型別的Annotation。如下程式碼是錯誤的。
//程式碼錯誤,不可以使用相同註解在一個程式元素上。
 @MyTag(name="liang")
@MyTag(name="huan")
public void info(){
 }

7.1 java8之前實現思路

  • 要想達到使用多個註解的目的,可以使用註解”容器“:其實就是新定義一個註解DupMyTag ,讓這個DupMyTag 註解的成員變數value的型別為註解MyTag陣列。這樣就可以通過註解DupMyTag 使用多個註解MyTag了。換個思路實現,只是書寫形式不一樣而已。

操作步驟2步:1編寫需要重複的註解@MyTag,上面定義過了。2.編寫”容器“註解DupMyTag 。

  • 如下DupMyTag 註解:
@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.METHOD)
public @interface DupMyTag {
    //成員變數為MyTag陣列型別
    MyTag[] value();
}
  • 使用@DupMyTag,為@DupMyTag 註解的成員變數設定多個@MyTag註解,從而達到效果。
//程式碼正確,換個思路實現,在同一個程式元素上使用了多個相同的註解MyTag
 @DupMyTag ({ @MyTag(name="liang"),@MyTag(name="huan",age=18)})
public void info(){
 }

列印註解輸出內容如下:

@annotation.DupMyTag(value=[@annotation.MyTag(age=25, name=liang), @annotation.MyTag(age=18, name=huan)])

**結論:通過新定義一個容器註解,來實現使用多個相同註解的目的,只是書寫形式不能達到期待效果而已,要想書寫形式能達到期待效果需要使用java8之後的@Repeatable元註解。
**

注:”容器“註解的保留期Retention必須比它所包含註解的保留期更長,否則編譯報錯

7.2 java8之後

  • java8之後新增了@Repeatable元註解,用來開發重複註解,其有一個必填Class型別變數value。

  • 同樣,還是需要新定義一個註解@DupMyTag。和上面定義的一樣。不一樣的是@Repeatable元註解需要加在@MyTag上,value值設定為DupMyTag.class,開發便完成。

操作步驟2步:1編寫需要重複的註解@MyTag,如下。2.編寫”容器“註解DupMyTag ,上面定義過了

  • 如下:通過@Repeatable定義了一個重複註解@MyTag。
//定義帶成員變數註解MyTag
@Repeatable(DupMyTag.class)
@Rentention(RetentionPolicy.RUNTIME)
@Method(ElementType.METHOD)
public @interface MyTag{
  //定義兩個成員變數,以方法的形式定義
  String name();
  int age() default 32;
}
  • 使用,書寫形式達到了理想效果,當然上面的形式依然可以使用
@MyTag(name="liang")
@MyTag(name="huan",age =18)
public void info(){
}
//兩種形式都可以
@DupMyTag ({ @MyTag(name="liang"),@MyTag(name="huan",age=18)})
public void info(){
}
  • 原理:系統依然還是將兩個MyTag註解作為DupMyTag的value成員變數的陣列元素,只是書寫形式多了一種而已

  • 獲取註解方法
    上面程式碼通過getDeclaredAnnotationsByType(MyTag.class)和getDeclaredAnnotation(DupMyTag.class)兩個方法都能獲取到值,只是結果不一樣如下:

@annotation.MyTag(age=25, name=liang)
@annotation.MyTag(age=18, name=huan)
@annotation.DupMyTag(value=[@annotation.MyTag(age=25, name=liang), @annotation.MyTag(age=18, name=huan)])

8. Java8新增的Type Annotation註解

8.1 介紹

  • 目的:以前的註解只能用在包、類、構造器、方法、成員變數、引數、區域性變數。如果想在:建立物件(通過new建立)、型別轉換、使用implements實現介面、使用throws宣告丟擲異常的位置使用註解就不行了。而Type Annotation註解就為了這個而來。

  • 抽象表述: java為ElementType列舉增加了TYPE_PARAMETER、TYPE_USE兩個列舉值。@Target(TYPE_USE)修飾的註解稱為Type Annotation(型別註解),Type Annotation可用在任何用到型別的地方。*

8.2 案例

  • 定義一個型別註解NotNull
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull {
    String value() default "";
}
  • 使用
//implements實現介面中使用Type Annotation
public class Test implements @NotNull(value="Serializable") Serializable{
    
        //泛型中使用Type Annotation  、   丟擲異常中使用Type Annotation
    public  void foo(List<@NotNull String> list) throws @NotNull(value="ClassNotFoundException") ClassNotFoundException {
        //建立物件中使用Type Annotation
        Object obj =new @NotNull String("annotation.Test");
        //強制型別轉換中使用Type Annotation
        String str = (@NotNull String) obj;
    }
}
  • 編寫處理註解的處理器。

  • java8提供AnnotatedType介面,該介面用來代表被註解修飾的型別。該介面繼承AnnotatedElement介面。同時多了一個public Type getType()方法,用於返回註解修飾的型別。

  • 以下處理器只處理了類實現介面處的註解和throws宣告丟擲異常處的註解。

/*
類說明 NotNull註解處理器,只處理了implements實現接口出註解、throws宣告丟擲異常出的註解。
*/
public class NotNullAnnotationProcessor {
    
    public static void process(String className) throws ClassNotFoundException{
        try {
            Class clazz =Class.forName(className);
            //獲取類繼承的、帶註解的介面
            AnnotatedType[] aInterfaces =clazz.getAnnotatedInterfaces();
            print(aInterfaces);
            
            Method method = clazz.getMethod("foo");
            //獲取方法上丟擲的帶註解的異常
            AnnotatedType[] aExceptions =method.getAnnotatedExceptionTypes();
            print(aExceptions);
            
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
    /**
     * 列印帶註解型別
     * @param array
     */
    public static void print(AnnotatedType[] array){
        for( AnnotatedType at : array){
            Type type =at.getType();//獲取基礎型別
            Annotation[] ans =at.getAnnotations();//獲取註解
            //列印型別
            System.out.println(type);
            //列印註解
            for( Annotation an : ans){
                System.out.println(an);
            }
            System.out.println("------------");
        }
    }
}

列印結果

interface java.io.Serializable
@annotation.NotNull(value=Serializable)
------------
class java.lang.ClassNotFoundException
@annotation.NotNull(value=ClassNotFoundException)
------------

9. 編譯時處理Annotation

9.1 需求

  • 有過Hibernate開發經驗的朋友可能知道每寫一個Java檔案,還必須額外地維護一個Hibernate對映檔案(一個名為*.hbm.xml的檔案,當然可以有一些工具可以自動生成)下面將使用Annotation來簡化這步操作。思路:自定義修飾類的註解,在實體類上使用註解,編寫註解處理器:根據原始檔中的類上的註解,生成*.hbm.xml檔案,使用java提供的編譯命令javac執行註解處理器。關鍵:編寫註解處理器。

9.2可用api

  • 我們知道前面的註解處理器處理的都是@Retention(RetentionPolicy.RUNTIME)的註解,使用的是反射技術。而生成的*hbm.xml檔案是需要在編譯階段完成。為此java在java7之前提供了apt工具及api,在java7及之後提供了JSR269 api。

9.3 apt和jsr269的作用

  • APT是一種處理註釋的工具,它對原始碼檔案進行檢測,並找出原始檔中所包含的Annotation資訊,然後針對Annotation資訊進行額外的處理。
  • APT處理器在處理Annotation時可以根據原始檔中的Annotation生成額外的原始檔和其它的檔案(檔案具體內容由Annotation處理器的編寫者決定),APT還會編譯生成的原始檔和原來的原始檔,將它們一起生成class檔案.使用APT主要的目的是簡化開發者的工作量。
  • 因為APT可以編譯程式原始碼的同時,生成一些附屬檔案(比如原始檔、類檔案、程式釋出描述檔案等),這些附屬檔案的內容也都是與原始碼相關的,換句話說,使用APT可以代替傳統的對程式碼資訊和附屬檔案的維護工作。
  • APT的相關api都在com.sun.mirror 包下,在jdk7及之後,apt的相關api就被廢除了,代替的是JSR269。JSR269API文件下載。JSR269的api在 javax.annotation.processing and javax.lang.model包下。
    所以以後開發註解處理器使用jsr269提供的api就可以了。

JSR269描述

9.4實現

9.5 使用apt實現

使用apt實現編譯時處理Annotation

9.6 使用JSR269實現

  • 執行環境jdk1.8

  • Java提供的javac.exe工具有一個-processor選項,該選項可指定一個Annotation處理器,如果在編譯java原始檔的時候通過該選項指定了Annotation處理器,那麼這個Annotation處理器,將會在編譯時提取並處理Java原始檔中的Annotation。

  • 每個Annotation處理器都需要實現javax.annotation.processing包下的Processor介面。不過實現該介面必須實現它裡面所有方法,因此通常採用繼承AbstractProcessor的方式來實現Annotation處理器,一個Annotation處理器可以處理一種或多種Annotation型別。

  • 之前的錯誤認識:之前以為-processor選項需要指定註解處理器是一個*.java檔案,其實是一個.class檔案,既然是.class檔案,那麼肯定是編譯過後的,所以需要單獨寫一個處理器程式annotation-processor,打成一個jar包,然後在使用註解的程式annotation中加入註解處理器依賴包annotation-processor.jar,在編譯的時候指定處理器類即可。下面我會分別演示通過javac 命令和maven命令如何進行操作。

  • 下面的專案會使用maven來構建,如果不是使用maven也可以,因為我也會演示如何通過javac 命令來執行註解處理器。


9.6.1 註解處理器程式annotation-processor

  • 下面將定義三個Annotation型別,分別用於修飾持久化類,標識屬性和普通屬性。

修飾id註解

package com.zlcook.processor.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//修飾id註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Id {
    String column();    //該id屬性對應表中的列名
    String type();      //id屬性型別
    String generator(); //使用的策略
}

修飾屬性註解

package com.zlcook.processor.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//修飾屬性註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Property {
    String column();    //該屬性對應表中的列名
    String type();      //id屬性型別
}

修飾實體類註解

package com.zlcook.processor.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//修飾實體類註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Persistent {
    String table();       //資料庫中表名
}

  • 處理上面三個註解的處理器HibernateAnnotationProcessor,根據註解生成對應的*.hbm.xml檔案
package com.zlcook.processor;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import com.zlcook.processor.annotation.Id;
import com.zlcook.processor.annotation.Persistent;
import com.zlcook.processor.annotation.Property;

/**
* 類說明:hiberante註解處理器,用於根據實體bean的註解生成*.hbm.xml檔案,處理時期在編譯階段。
*/
public class HibernateAnnotationProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        // TODO Auto-generated method stub
        super.init(processingEnv);
        System.out.println("HibernateAnnotationProcessor註解處理器初始化完成..............");
    }
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        
        //定義一個檔案輸出流,用於生成額外的檔案
        PrintStream ps = null;
        try{
            //遍歷每個被@Persistent修飾的class檔案,使用RoundEnvironment來獲取Annotation資訊
            for( Element t : roundEnv.getElementsAnnotatedWith(Persistent.class)){
                //獲取正在處理的類名
                Name clazzName = t.getSimpleName();
                //獲取類定義前的@Persistent Annotation
                Persistent per = t.getAnnotation(Persistent.class);
                //建立檔案輸出流
                String fileName =clazzName+".hbm.xml";
                ps = new PrintStream(new FileOutputStream(fileName));
                 // 執行輸出
                 ps.println("<?xml version=\"1.0\"?>");
                 ps.println("<!DOCTYPE hibernate-mapping");
                 ps.println(" PUBLIC \"-// Hibernate/Hibernate Ma  pping DTD 3.0//EN\"");
                 ps.println(" \"http:// hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\">");
                 ps.println("<hibernate-mapping>");
                 ps.print(" <class name=\"" + t);
                 // 輸出per的table()的值
                 ps.println("\" table=\"" + per.table() + "\">");
                 //獲取@Persistent修改類的各個屬性欄位。t.getEnclosedElements()獲取該Elemet裡定義的所有程式單元
                 for(Element ele : t.getEnclosedElements()){
                     
                     //只處理成員變數上的Annotation,ele.getKind()返回所代表的的程式單元
                     if( ele.getKind() == ElementKind.FIELD){
                        //被id註解修飾的欄位
                         Id idAno= ele.getAnnotation(Id.class);
                         if( idAno != null){
                             String column =idAno.column();
                             String type =idAno.type();
                             String generator = idAno.generator();
                             // 執行輸出
                               ps.println(" <id name=\"" + ele.getSimpleName() + "\" column=\"" + column + "\" type=\"" + type + "\">");
                               ps.println(" <generator class=\"" + generator + "\"/>");
                               ps.println(" </id>");
                         }
                         
                         //被Property註解修飾的欄位
                         Property p = ele.getAnnotation(Property.class);
                         if( p !=null){
                             // 執行輸出
                             ps.println(" <property name=\"" + ele.getSimpleName() + "\" column=\"" + p.column() + "\"type=\"" + p.type() + "\"/>");
                         }
                     }
                 }// end for
                 ps.println(" </class>");
                 ps.println("</hibernate-mapping>");
            }// end for 
            
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            if(ps!=null){
                try{
                    ps.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        }
        return true;
    }
    /** 
     * 這裡必須指定,這個註解處理器是註冊給哪個註解的。注意,它的返回值是一個字串的集合,包含本處理器想要處理的註解型別的合法全稱 
     * @return  註解器所支援的註解型別集合,如果沒有這樣的型別,則返回一個空集合 
     */  
    @Override  
    public Set<String> getSupportedAnnotationTypes() {  
        Set<String> annotataions = new LinkedHashSet<String>();  
        annotataions.add(Id.class.getCanonicalName());  
        annotataions.add(Property.class.getCanonicalName());  
        annotataions.add(Persistent.class.getCanonicalName());  
        return annotataions;  
    }  
  
    /** 
     * 指定使用的Java版本,通常這裡返回SourceVersion.latestSupported(),預設返回SourceVersion.RELEASE_6 
     * @return  使用的Java版本 
     */
    @Override  
    public SourceVersion getSupportedSourceVersion() {  
        return SourceVersion.latestSupported();  
    }  
}

  • 註解程式寫完打包成jar檔案。

  • 打包成jar檔案為使用註解處理器的程式提供依賴。

  • 使用maven構建直接使用mvn install,這樣就將專案打包成jar依賴到本地倉庫中了。

  • 使用java命令打包成jar檔案:先用javac編譯成.class檔案,在使用jar命令打包成jar檔案。

  • 使用java命令打包成jar檔案

  • 原始檔位置:E:\EclipseWorkspace\Cnu\annotation-processor\src\main\java,編譯後.class檔案存放到classes資料夾下,使用javac命令編譯原始碼需要指定.java檔案,為了避免在命令列中敲太多程式碼,所以將要編譯的原始碼檔案都列在了sources.list檔案中。

原始碼檔案及編譯後文件存放位置

source.list檔案內容

  • 執行編譯命令javac
    javac命令中指定UTF-8編碼、編譯後文件存放位置、需要編譯的原始檔
E:\EclipseWorkspace\Cnu\annotation-processor\src\main\java>javac -encoding UTF-8
 -d classes @sources.list
  • 執行打包命令jar
    將classes中的編譯檔案,打包成annotation-processor.jar檔案。進入到classes目錄中執行如下jar命令
E:\EclipseWorkspace\Cnu\annotation-processor\src\main\java\classes>jar -cvf annotation-processor.jar com

9.6.2 註解使用程式annotation

  • 新增annotation-processor.jar依賴
  • 註解處理程式寫完並打成了jar包,將jar引入到annotation中使用。
  • 使用maven則在pom.xml中宣告一個依賴。因為該依賴只在編譯階段才使用所以範圍採用provied。更多maven依賴範圍
<dependency>
          <groupId>com.zlcook.processor</groupId>
          <artifactId>annotation-processor</artifactId>
          <version>0.0.5-SNAPSHOT</version>
          <scope>provided</scope>
      </dependency>
  • 沒有使用maven構建,只要保證執行專案時annotation-processor.jar在classpath路徑中就行。根據你是用的開發工具而定,使用eclipse則將jar新增到編譯路徑中。

  • 編寫專案annotation

  • 為了演示自定義註解和註解處理的作用:在編譯時根據註解生成*.hbm.xml檔案,所以寫一個類Person就可以了。程式碼如下:

package com.zlcook.annotation.bean;
import com.zlcook.processor.annotation.Id;
import com.zlcook.processor.annotation.Persistent;
import com.zlcook.processor.annotation.Property;

/**
* @author 周亮 
* @version 建立時間:2017年2月19日 下午10:05:05
* 類說明:使用註解完成對映的實體類
*/
@Persistent(table="person_inf")
public class Person {
     @Id(column = "person_id", type = "integer", generator = "identity")
     private int id;
     @Property(column = "person_name", type = "string")
     private String name;
     @Property(column = "person_age", type = "integer")
     private int age;
     public int getId() {
      return id;
     }
     public void setId(int id) {
      this.id = id;
     }
     public String getName() {
      return name;
     }
     public void setName(String name) {
      this.name = name;
     }
     public int getAge() {
      return age;
     }
     public void setAge(int age) {
      this.age = age;
     }
}

9.6.3 執行效果演示

  • 下面就使用javac命令和maven命令編譯annotation專案,來演示HibernateAnnotationProcessor處理器的效果。看能不能在編譯期生成Person.hbm.xml檔案。

  • javac編譯

    • 將annotation-processor.jar拷貝到annotaion的原始碼位置,當然你也可以拷貝到其它地方,主要為了引用方便。再新建一個存放編譯檔案的資料夾classes。如下:

       

      編譯器檔案情況

  • 在該目錄下執行javac 命令
    javac命令中指定UTF-8編碼、編譯後文件存放位置、編譯過程中依賴的檔案、註解處理器類、需要編譯的原始檔

E:\EclipseWorkspace\Cnu\annotation\src\main\java>javac -encoding UTF-8 -d classes -classpath annotation-processor.jar -processor com.zlcook.processor.HibernateAnnotationProcessor com/zlcook/annotation/bean/Person.java
  • 執行後效果
    當前目錄下出現了一個Person.hbm.xml檔案

     

    Paste_Image.png

  • Maven編譯

  • 使用maven編譯,唯一需要動的的就是指明編譯過程中需要的註解處理程式HibernateAnnotationProcessor。為此需要設定maven-compiler-plugin外掛中compiler目標的引數。

  • 在pom.xml中設定如下:

<build>
   <plugins>
    <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <executions>
          <execution>
            <id>default-compile</id>
            <phase>compile</phase>
            <goals>
              <goal>compile</goal>
            </goals>
            <configuration>
             <source>1.8</source>
             <target>1.8</target>
              <annotationProcessors>
                <annotationProcessor>com.zlcook.processor.HibernateAnnotationProcessor</annotationProcessor>
             </annotationProcessors> 
            </configuration>
          </execution>
        </executions>
      </plugin>
   </plugins>
  </build>
  • 執行maven命令
mvn clean compile

執行完成後在專案根目錄下就出現了Person.hbm.xml檔案。

  • Person.hbm.xml內容如下:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
 PUBLIC "-// Hibernate/Hibernate Ma  pping DTD 3.0//EN"
 "http:// hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
 <class name="com.zlcook.annotation.bean.Person" table="person_inf">
 <id name="id" column="person_id" type="integer">
 <generator class="identity"/>
 </id>
 <property name="name" column="person_name"type="string"/>
 <property name="age" column="person_age"type="integer"/>
 </class>
</hibernate-mapping>