
本文章涉及代碼已放到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ā)生堆污染。
例如:下面代碼引起堆污染,會給出警告
-
堆污染:把一個不帶泛型的對象賦給一個帶泛型的變量時就會發(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就可以了。

9.4實現(xiàn)
9.5 使用apt實現(xiàn)
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文件中。


- 執(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)--注解處理器
留言
喜歡就點個贊吧,多謝鼓勵,如果有什么不懂的一起探討一下吧。

