Java:Annotation(注解)--原理到案例

圖片取自文末推薦文章

本文章涉及代碼已放到github上annotation-study

1.Annotation為何而來

What:Annotation干嘛的

  • JDK5開始,java增加了對元數(shù)據(jù)(MetaData)的支持,怎么支持?答:通過Annotation(注解)來實現(xiàn)。Annotation提供了為程序元素設置元數(shù)據(jù)的方法。元數(shù)據(jù):描述數(shù)據(jù)的數(shù)據(jù)。

  • Annotation可以為哪些程序元素設置元數(shù)據(jù)呢? Annotation提供了一種為程序元素設置元數(shù)據(jù)的方法,包括修飾包、類、構造器、方法、成員變量、參數(shù)、局部變量的聲明。元數(shù)據(jù)的信息被存儲在Annotation的“name=value”對中。

  • Annotation怎么實現(xiàn)設置元數(shù)據(jù)?程序如何讀取這些元數(shù)據(jù)?答:元數(shù)據(jù)的信息被存儲在Annotation的“name=value”對中。Annotation是一個接口,程序可以通過反射來獲取指定程序元素的Annotation對象,然后通過Annotation對象來取得注解里的元數(shù)據(jù)。

  • Annotation不影響程序代碼的執(zhí)行,無論增加、刪除Annotation,代碼都始終如一的執(zhí)行。如果希望讓程序中的Annotation在運行時起一定的作用,只有通過某種配套工具對Annotation中的信息進行訪問和處理。jdk7之前訪問和處理Annotation的工具統(tǒng)稱APT(Annotation Processing Tool)(jdk7后就被廢除了),jdk7及之后采用了JSR 269 API。相關原因官方說明 、 原因

  • 結論:java想給程序元素提供元數(shù)據(jù)支持,于是創(chuàng)造了Annotation來實現(xiàn)這個目標。

注解的使用案例

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

Why:為什么要提供元數(shù)據(jù)支持

  • 通過Annotation設置的元數(shù)據(jù)在什么時候被讀???讀取能干嘛?答:Annotation就像代碼里的特殊標記,這些標記可以在編譯、類加載、運行時被讀取。讀取到了程序元素的元數(shù)據(jù),就可以執(zhí)行相應的處理。通過注解,程序開發(fā)人員可以在不改變原有邏輯的情況下,在源代碼文件中嵌入一些補充信息。代碼分析工具、開發(fā)工具和部署工具可以通過解析這些注解獲取到這些補充信息,從而進行驗證或者進行部署等。

  • 比如:上面代碼,讀取到 id變量上面有@GeneratedValue(strategy=GenerationType.AUTO)注解,并且注解提供了strategy=GenerationType.AUTO這樣的元數(shù)據(jù)信息,那么程序就會為id設置一個自增的值。讀取到Book類上面有一個@Entity注解,程序就會認為這是一個持久化類,就會做一些持久化的處理。

  • 不使用Annotation怎么為程序元素提供元數(shù)據(jù)
    看來元數(shù)據(jù)在編程中還是能起到很大的作用的,如果沒有元數(shù)據(jù)還真的不好辦,比如上面代碼中id成員變量的元數(shù)據(jù)是“strategy=GenerationType.AUTO即采用自增策略”,如果沒有這個元數(shù)據(jù)支持,程序中怎么才能為id賦一個自增的值呢?憂愁。

  • 提供元數(shù)據(jù)只有通過Annotation才可以嗎?答:不是,通過配置文件也可以。比如還是上面代碼id這個變量,我現(xiàn)在想為它添加描述數(shù)據(jù)即元數(shù)據(jù),內容是:采用自增策略。這個信息通過Annotation來實現(xiàn)就是上面代碼的樣子。通過配置文件實現(xiàn)的話,比如采用xml格式配置文件。那么我可以在文件中配置<property-MetaData class="Book " property="id" metadata="auto">。哈哈!比如我就定一個規(guī)則:class表示類,property表示類的某個屬性,metadata是屬性的元數(shù)據(jù)。程序在啟動時通過讀取這個文件的信息就可以知道id變量的元數(shù)據(jù)了,知道元數(shù)據(jù)就可以做相應處理了。當然,通過配置文件還是沒有注解方便。


知道元數(shù)據(jù)在編程中的重要性和提供元數(shù)據(jù)的方法Annotation了,那么就來學習Annotation吧。

  • 提示:有些注解只是為了防止我們犯低級錯誤,通過這些注解,讓編譯器在編譯期就可以檢查出一些低級錯誤,對于這些注解,可以加或者不加,當然還有很多其他注解都是起輔助編程作用。但是有一些注解的作用很重要,不加的話就實現(xiàn)不了一些功能,比如,數(shù)據(jù)持久化操作中,通過@Entity注解來標識持久化實體類,如果不使用該注解程序就識別不了持久化實體類。

2.基本Annotation

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

  • @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新增):去除“堆污染”警告
    • 堆污染:把一個不帶泛型的對象賦給一個帶泛型的變量時就會發(fā)生堆污染。
      例如:下面代碼引起堆污染,會給出警告
List l2 = new ArrayList<Number>();
List<String> ls = l2;    

三種方式去掉上面方法產生的警告
1.使用注解@SafeVarargs修飾引發(fā)該警告的方法或構造器。
2.使用@SuppressWarnings("unchecked") 修飾。
3.使用編譯器參數(shù)命令:-Xlint:varargs

  • @Functionlnterface (java8新增):修飾函數(shù)式接口
    使用該注解修飾的接口必須是函數(shù)式接口,不然編譯會出錯。那么什么是函數(shù)式接口?答:如果接口中只有一個抽象方法(可以包含多個default方法或static方法),就是函數(shù)式接口。
    如:
@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):和元數(shù)據(jù)一樣,修飾注解的注解
  • 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) //方法參數(shù)
  • @Target(ElementType.CONSTRUCTOR) //構造函數(shù)
  • @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稱為元數(shù)據(jù)Annotation,因為他們提供更多元數(shù)據(jù)。

5.4提取Annotation信息

  • 使用Annotation修飾了類、方法、成員變量等程序元素之后,這些Annotation不會自己生效,必須由開發(fā)者通過API來提取并處理Annotation信息。

  • Annotation接口是所有注解的父接口。

  • 思路:通過反射獲取Annotation,將Annotation轉換成具體的注解類,在調用注解類定義的方法獲取元數(shù)據(jù)信息。

獲取Annotation

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

  • Class:類定義。

  • Constructor:構造器定義

  • Field:類的成員變量定義

  • Method:類的方法定義。

  • Package:類的包定義。

  • 由此可見,AnnotatedElement接口的實現(xiàn)類都是一些反射技術設計到的類,所以訪問Annotation信息也是通過反射技術來實現(xiàn)的。

  • java.lang.reflect包下還包含實現(xiàn)反射功能的工具類,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注解的元數(shù)據(jù)。

  • 實現(xiàn):正如我們所知,僅在程序中使用注解是不起任何作用的,必須使用注解處理工具來處理程序中的注解。下面就寫一個注解處理類。處理注解的思路如下:通過反射獲取Test的類描述類Class,然后在獲取其info方法描述類Method,因為Method實現(xiàn)了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之前實現(xiàn)思路

  • 要想達到使用多個注解的目的,可以使用注解”容器“:其實就是新定義一個注解DupMyTag ,讓這個DupMyTag 注解的成員變量value的類型為注解MyTag數(shù)組。這樣就可以通過注解DupMyTag 使用多個注解MyTag了。換個思路實現(xiàn),只是書寫形式不一樣而已。

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

  • 如下DupMyTag 注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.METHOD)
public @interface DupMyTag {
    //成員變量為MyTag數(shù)組類型
    MyTag[] value();
}
  • 使用@DupMyTag,為@DupMyTag 注解的成員變量設置多個@MyTag注解,從而達到效果。
//代碼正確,換個思路實現(xiàn),在同一個程序元素上使用了多個相同的注解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)])

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

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

7.2 java8之后

  • java8之后新增了@Repeatable元注解,用來開發(fā)重復注解,其有一個必填Class類型變量value。

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

操作步驟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(){
}
  • 原理:系統(tǒng)依然還是將兩個MyTag注解作為DupMyTag的value成員變量的數(shù)組元素,只是書寫形式多了一種而已

  • 獲取注解方法
    上面代碼通過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 介紹

  • 目的:以前的注解只能用在包、類、構造器、方法、成員變量、參數(shù)、局部變量。如果想在:創(chuàng)建對象(通過new創(chuàng)建)、類型轉換、使用implements實現(xiàn)接口、使用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實現(xiàn)接口中使用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 {
        //創(chuàng)建對象中使用Type Annotation
        Object obj =new @NotNull String("annotation.Test");
        //強制類型轉換中使用Type Annotation
        String str = (@NotNull String) obj;
    }
}
  • 編寫處理注解的處理器。

  • java8提供AnnotatedType接口,該接口用來代表被注解修飾的類型。該接口繼承AnnotatedElement接口。同時多了一個public Type getType()方法,用于返回注解修飾的類型。

  • 以下處理器只處理了類實現(xiàn)接口處的注解和throws聲明拋出異常處的注解。

/*
類說明 NotNull注解處理器,只處理了implements實現(xiàn)接口出注解、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開發(fā)經驗的朋友可能知道每寫一個Java文件,還必須額外地維護一個Hibernate映射文件(一個名為*.hbm.xml的文件,當然可以有一些工具可以自動生成)下面將使用Annotation來簡化這步操作。思路:自定義修飾類的注解,在實體類上使用注解,編寫注解處理器:根據(jù)源文件中的類上的注解,生成*.hbm.xml文件,使用java提供的編譯命令javac執(zhí)行注解處理器。關鍵:編寫注解處理器。

9.2可用api

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

9.3 apt和jsr269的作用

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

9.4實現(xiàn)

9.5 使用apt實現(xiàn)

使用apt實現(xiàn)編譯時處理Annotation

9.6 使用JSR269實現(xiàn)

  • 運行環(huán)境jdk1.8

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

  • 每個Annotation處理器都需要實現(xiàn)javax.annotation.processing包下的Processor接口。不過實現(xiàn)該接口必須實現(xiàn)它里面所有方法,因此通常采用繼承AbstractProcessor的方式來實現(xiàn)Annotation處理器,一個Annotation處理器可以處理一種或多種Annotation類型。

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

  • 下面的項目會使用maven來構建,如果不是使用maven也可以,因為我也會演示如何通過javac 命令來執(zhí)行注解處理器。


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();       //數(shù)據(jù)庫中表名
}

  • 處理上面三個注解的處理器HibernateAnnotationProcessor,根據(jù)注解生成對應的*.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注解處理器,用于根據(jù)實體bean的注解生成*.hbm.xml文件,在編譯階段執(zhí)行。
*/
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);
                //創(chuàng)建文件輸出流
                String fileName =clazzName+".hbm.xml";
                ps = new PrintStream(new FileOutputStream(fileName));
                 // 執(zhí)行輸出
                 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();
                             // 執(zhí)行輸出
                               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){
                             // 執(zhí)行輸出
                             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文件內容
  • 執(zhí)行編譯命令javac
    javac命令中指定UTF-8編碼、編譯后文件存放位置、需要編譯的源文件
E:\EclipseWorkspace\Cnu\annotation-processor\src\main\java>javac -encoding UTF-8
 -d classes @sources.list
  • 執(zhí)行打包命令jar
    將classes中的編譯文件,打包成annotation-processor.jar文件。進入到classes目錄中執(zhí)行如下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路徑中就行。根據(jù)你是用的開發(fā)工具而定,使用eclipse則將jar添加到編譯路徑中。

  • 編寫項目annotation

  • 為了演示自定義注解和注解處理的作用:在編譯時根據(jù)注解生成*.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 創(chuàng)建時間: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處理器的效果??茨懿荒茉诰幾g期生成Person.hbm.xml文件。

  • javac編譯

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


      編譯器文件情況
  • 在該目錄下執(zhí)行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
  • 執(zhí)行后效果
    當前目錄下出現(xiàn)了一個Person.hbm.xml文件


    Paste_Image.png
  • Maven編譯

  • 使用maven編譯,唯一需要動的的就是指明編譯過程中需要的注解處理程序HibernateAnnotationProcessor。為此需要設置maven-compiler-plugin插件中compiler目標的參數(shù)。

  • 在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>
  • 執(zhí)行maven命令
mvn clean compile

執(zhí)行完成后在項目根目錄下就出現(xiàn)了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>

本文章涉及代碼已放到github上annotation-study

參考文章:
Java注解(3)-注解處理器(編譯期|RetentionPolicy.SOURCE)
jar 打包命令詳解
如何用javac 和java 編譯運行整個Java工程
深入理解Java:注解(Annotation)自定義注解入門
深入理解Java:注解(Annotation)--注解處理器

留言

喜歡就點個贊吧,多謝鼓勵,如果有什么不懂的一起探討一下吧。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一種元程序中的元素關聯(lián)任何信息和...
    九尾喵的薛定諤閱讀 3,393評論 0 2
  • 整體Retrofit內容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李頭閱讀 8,659評論 4 31
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評論 19 139
  • Java 中的注解(Annotation) 是一個很方便的特性在Spring當中得到了大量的應用 , 我們也可以開...
    _秋天閱讀 10,154評論 3 22
  • 簡書,就是簡單而方便的書寫;可以記錄自己身邊的事情,簡單而又有感覺的東西! 工作是生活的一部分,生活也是工作...
    螞蟻聽雨閱讀 408評論 0 1

友情鏈接更多精彩內容