Java 之註解(Annotation)
1.Annotation為何而來
What:Annotation幹嘛的
-
JDK5開始,java增加了對元資料(MetaData)的支援,怎麼支援?答:通過Annotation(註解)來實現。Annotation提供了為程式元素設定元資料的方法。元資料:描述資料的資料。
-
Annotation可以為哪些程式元素設定元資料呢? Annotation提供了一種為程式元素設定元資料的方法,包括修飾包、類、構造器、方法、成員變數、引數、區域性變數的宣告。元資料的資訊被儲存在Annotation的“name=value”對中。
-
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實現
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>