Java Annotation 高級應(yīng)用
作者:cleverpig
版權(quán)聲明:本文可以自由轉(zhuǎn)載,轉(zhuǎn)載時請務(wù)必以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲明 作者:cleverpig(作者的Blog:http://blog./page/cleverpig) 原文:http://www./resource/article/44/44062_Java+Annotation+Apt.html 關(guān)鍵字:java,annotation,apt
前言: 前不久在matrix上先后發(fā)表了《java annotation 入門》、《java annotation 手冊》兩篇文章,比較全面的對java annotation的語法、原理、使用三方面進(jìn)行了闡述。由于《入門》中的簡單例程雖然簡單明了的說明了annotation用法,但給大家的感覺可能是意猶未見,所以在此行文《java annotation高級應(yīng)用》,具體實例化解釋annotation和annotation processing tool(APT)的使用。望能對各位的有所幫助。
一、摘要: 《java annotation高級應(yīng)用》具體實例化解釋annotation和annotation processing tool(APT)的使用。望能對各位的有所幫助。本文列舉了用于演示annotation的BRFW演示框架、演示APT的apt代碼實例,并對其進(jìn)行較為深度的分析,希望大家多多提意見。
二、annotation實例分析 1.BRFW(Beaninfo Runtime FrameWork)定義: 本人編寫的一個annotation功能演示框架。顧名思義,BRFW就是在運行時取得bean信息的框架。
2.BRFW的功能: A.源代碼級annotation:在bean的源代碼中使用annotation定義bean的信息; B.運行時獲取bean數(shù)據(jù):在運行時分析bean class中的annotation,并將當(dāng)前bean class中field信息取出,功能類似xdoclet; C.運行時bean數(shù)據(jù)的xml綁定:將獲得的bean數(shù)據(jù)構(gòu)造為xml文件格式展現(xiàn)。熟悉j2ee的朋友知道,這個功能類似jaxb。
3.BRFW框架: BRFW主要包含以下幾個類: A.Persistent類:定義了用于修飾類的固有類型成員變量的annotation。 B.Exportable類:定義了用于修飾Class的類型的annotation。 C.ExportToXml類:核心類,用于完成BRFW的主要功能:將具有Exportable Annotation的bean對象轉(zhuǎn)換為xml格式文本。 D.AddressForTest類:被A和B修飾過的用于測試目的的地址bean類。其中包含了地址定義所必需的信息:國家、省級、城市、街道、門牌等。 E.AddressListForTest類:被A和B修飾過的友人通訊錄bean類。其中包含了通訊錄所必備的信息:友人姓名、年齡、電話、住址(成員為AddressForTest類型的ArrayList)、備注。需要說明的是電話這個bean成員變量是由字符串類型組成的ArrayList類型。由于朋友的住址可能不唯一,故這里的住址為由AddressForTest類型組成的ArrayList。 從上面的列表中,可以發(fā)現(xiàn)A、B用于修飾bean類和其類成員;C主要用于取出bean類的數(shù)據(jù)并將其作xml綁定,代碼中使用了E作為測試類;E中可能包含著多個D。 在了解了這個簡單框架后,我們來看一下BRFW的代碼吧!
4.BRFW源代碼分析: A.Persistent類: 清單1:
package com.bjinfotech.practice.annotation.runtimeframework;
import java.lang.annotation.*;
/** * 用于修飾類的固有類型成員變量的annotation * @author cleverpig * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Persistent { String value() default ""; }
B.Exportable類: 清單2:
package com.bjinfotech.practice.annotation.runtimeframework;
import java.lang.annotation.*;
/** * 用于修飾類的類型的annotation * @author cleverpig * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Exportable { //名稱 String name() default ""; //描述 String description() default ""; //省略name和description后,用來保存name值 String value() default ""; }
C.AddressForTest類: 清單3:
package com.bjinfotech.practice.annotation.runtimeframework;
/** * 用于測試的地址類 * @author cleverpig * */ @Exportable("address") public class AddressForTest { //國家 @Persistent private String country=null; //省級 @Persistent private String province=null; //城市 @Persistent private String city=null; //街道 @Persistent private String street=null;
//門牌 @Persistent private String doorplate=null; public AddressForTest(String country,String province, String city,String street,String doorplate){ this.country=country; this.province=province; this.city=city; this.street=street; this.doorplate=doorplate; } }
D.AddressListForTest類: 清單4:
package com.bjinfotech.practice.annotation.runtimeframework;
import java.util.*;
/** * 友人通訊錄 * 包含:姓名、年齡、電話、住址(多個)、備注 * @author cleverpig * */ @Exportable(name="addresslist",description="address list") public class AddressListForTest { //友人姓名 @Persistent private String friendName=null; //友人年齡 @Persistent private int age=0; //友人電話 @Persistent private ArrayList<String> telephone=null; //友人住址:家庭、單位 @Persistent private ArrayList<AddressForTest> AddressForText=null; //備注 @Persistent private String note=null; public AddressListForTest(String name,int age, ArrayList<String> telephoneList, ArrayList<AddressForTest> addressList, String note){ this.friendName=name; this.age=age; this.telephone=new ArrayList<String>(telephoneList); this.AddressForText=new ArrayList<AddressForTest>(addressList); this.note=note; } }
E.ExportToXml類: 清單5:
package com.bjinfotech.practice.annotation.runtimeframework;
import java.lang.reflect.Field; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.ArrayList;
/** * 將具有Exportable Annotation的對象轉(zhuǎn)換為xml格式文本 * @author cleverpig * */ public class ExportToXml { /** * 返回對象的成員變量的值(字符串類型) * @param field 對象的成員變量 * @param fieldTypeClass 對象的類型 * @param obj 對象 * @return 對象的成員變量的值(字符串類型) */ private String getFieldValue(Field field,Class fieldTypeClass,Object obj){ String value=null; try{ if (fieldTypeClass==String.class){ value=(String)field.get(obj); } else if (fieldTypeClass==int.class){ value=Integer.toString(field.getInt(obj)); } else if (fieldTypeClass==long.class){ value=Long.toString(field.getLong(obj)); } else if (fieldTypeClass==short.class){ value=Short.toString(field.getShort(obj)); } else if (fieldTypeClass==float.class){ value=Float.toString(field.getFloat(obj)); } else if (fieldTypeClass==double.class){ value=Double.toString(field.getDouble(obj)); } else if (fieldTypeClass==byte.class){ value=Byte.toString(field.getByte(obj)); } else if (fieldTypeClass==char.class){ value=Character.toString(field.getChar(obj)); } else if (fieldTypeClass==boolean.class){ value=Boolean.toString(field.getBoolean(obj)); } } catch(Exception ex){ ex.printStackTrace(); value=null; } return value; } /** * 輸出對象的字段,當(dāng)對象的字段為Collection或者M(jìn)ap類型時,要調(diào)用exportObject方法繼續(xù)處理 * @param obj 被處理的對象 * @throws Exception */ public void exportFields(Object obj) throws Exception{ Exportable exportable=obj.getClass().getAnnotation(Exportable.class); if (exportable!=null){ if (exportable.value().length()>0){ // System.out.println("Class annotation Name:"+exportable.value()); } else{ // System.out.println("Class annotation Name:"+exportable.name()); } } else{ // System.out.println(obj.getClass()+"類不是使用Exportable標(biāo)注過的"); } //取出對象的成員變量 Field[] fields=obj.getClass().getDeclaredFields(); for(Field field:fields){ //獲得成員變量的標(biāo)注 Persistent fieldAnnotation=field.getAnnotation(Persistent.class); if (fieldAnnotation==null){ continue; } //重要:避免java虛擬機(jī)檢查對私有成員的訪問權(quán)限 field.setAccessible(true); Class typeClass=field.getType(); String name=field.getName(); String value=getFieldValue(field,typeClass,obj); //如果獲得成員變量的值,則輸出 if (value!=null){ System.out.println(getIndent()+"<"+name+">\n" +getIndent()+"\t"+value+"\n"+getIndent()+"</"+name+">"); } //處理成員變量中類型為Collection或Map else if ((field.get(obj) instanceof Collection)|| (field.get(obj) instanceof Map)){ exportObject(field.get(obj)); } else{ exportObject(field.get(obj)); } } } //縮進(jìn)深度 int levelDepth=0; //防止循環(huán)引用的檢查者,循環(huán)引用現(xiàn)象如:a包含b,而b又包含a Collection<Object> cyclicChecker=new ArrayList<Object>(); /** * 返回縮進(jìn)字符串 * @return */ private String getIndent(){ String s=""; for(int i=0;i<levelDepth;i++){ s+="\t"; } return s; } /** * 輸出對象,如果對象類型為Collection和Map類型,則需要遞歸調(diào)用exportObject進(jìn)行處理 * @param obj * @throws Exception */ public void exportObject(Object obj) throws Exception{ Exportable exportable=null; String elementName=null; //循環(huán)引用現(xiàn)象處理 if (cyclicChecker.contains(obj)){ return; } cyclicChecker.add(obj); //首先處理Collection和Map類型 if (obj instanceof Collection){ for(Iterator i=((Collection)obj).iterator();i.hasNext();){ exportObject(i.next()); } } else if (obj instanceof Map){ for(Iterator i=((Map)obj).keySet().iterator();i.hasNext();){ exportObject(i.next()); } } else{
exportable=obj.getClass().getAnnotation(Exportable.class); //如果obj已經(jīng)被Exportable Annotation修飾過了(注意annotation是具有繼承性的), //則使用其name作為輸出xml的元素name if (exportable!=null){ if (exportable.value().length()>0){ elementName=exportable.value(); } else{ elementName=exportable.name(); } } //未被修飾或者Exportable Annotation的值為空字符串, //則使用類名作為輸出xml的元素name if (exportable==null||elementName.length()==0){ elementName=obj.getClass().getSimpleName(); } //輸出xml元素頭 System.out.println(getIndent()+"<"+elementName+">"); levelDepth++; //如果沒有被修飾,則直接輸出其toString()作為元素值 if (exportable==null){ System.out.println(getIndent()+obj.toString()); } //否則將對象的成員變量導(dǎo)出為xml else{ exportFields(obj); } levelDepth--; //輸出xml元素結(jié)尾 System.out.println(getIndent()+"</"+elementName+">"); } cyclicChecker.remove(obj); } public static void main(String[] argv){ try{ AddressForTest ad=new AddressForTest("China","Beijing", "Beijing","winnerStreet","10"); ExportToXml test=new ExportToXml(); ArrayList<String> telephoneList=new ArrayList<String>(); telephoneList.add("66608888"); telephoneList.add("66608889"); ArrayList<AddressForTest> adList=new ArrayList<AddressForTest>(); adList.add(ad); AddressListForTest adl=new AddressListForTest("coolBoy", 18,telephoneList,adList,"some words"); test.exportObject(adl); } catch(Exception ex){ ex.printStackTrace(); } } }
在ExportToXml類之前的類比較簡單,這里必須說明一下ExportToXml類:此類的核心函數(shù)是exportObject和exportFields方法,前者輸出對象的xml信息,后者輸出對象成員變量的信息。由于對象類型和成員類型的多樣性,所以采取了以下的邏輯:
在exportObject方法中,當(dāng)對象類型為Collection和Map類型時,則需要遞歸調(diào)用exportObject進(jìn)行處理; 而如果對象類型不是Collection和Map類型的話,將判斷對象類是否被Exportable annotation修飾過: 如果沒有被修飾,則直接輸出<對象類名>對象.toString()</對象類名>作為xml綁定結(jié)果的一部分; 如果被修飾過,則需要調(diào)用exportFields方法對對象的成員變量進(jìn)行xml綁定。
在exportFields方法中,首先取出對象的所有成員,然后獲得被Persisitent annotation修飾的成員。在其后的一句:field.setAccessible(true)是很重要的,因為bean類定義中的成員訪問修飾都是private,所以為了避免java虛擬機(jī)檢查對私有成員的訪問權(quán)限,加上這一句是必需的。接著后面的語句便是輸出<成員名>成員值</成員名>這樣的xml結(jié)構(gòu)。像在exportObject方法中一般,仍然需要判斷成員類型是否為Collection和Map類型,如果為上述兩種類型之一,則要在exportFields中再次調(diào)用exportObject來處理這個成員。
在main方法中,本人編寫了一段演示代碼:建立了一個由單個友人地址類(AddressForTest)組成的ArrayList作為通訊錄類(AddressForTest)的成員的通訊錄對象,并且輸出這個對象的xml綁定,運行結(jié)果如下:
清單6:
<addresslist> <friendName> coolBoy </friendName> <age> 18 </age> <String> 66608888 </String> <String> 66608889 </String> <address> <country> China </country> <province> Beijing </province> <city> Beijing </city> <street> winnerStreet </street> <doorplate> 10 </doorplate> </address> <note> some words </note> </addresslist>
三、APT實例分析: 1.何謂APT? 根據(jù)sun官方的解釋,APT(annotation processing tool)是一個命令行工具,它對源代碼文件進(jìn)行檢測找出其中的annotation后,使用annotation processors來處理annotation。而annotation processors使用了一套反射API并具備對JSR175規(guī)范的支持。 annotation processors處理annotation的基本過程如下:首先,APT運行annotation processors根據(jù)提供的源文件中的annotation生成源代碼文件和其它的文件(文件具體內(nèi)容由annotation processors的編寫者決定),接著APT將生成的源代碼文件和提供的源文件進(jìn)行編譯生成類文件。 簡單的和前面所講的annotation實例BRFW相比,APT就像一個在編譯時處理annotation的javac。而且從sun開發(fā)者的blog中看到,java1.6 beta版中已將APT的功能寫入到了javac中,這樣只要執(zhí)行帶有特定參數(shù)的javac就能達(dá)到APT的功能。
2.為何使用APT? 使用APT主要目的是簡化開發(fā)者的工作量,因為APT可以在編譯程序源代碼的同時,生成一些附屬文件(比如源文件、類文件、程序發(fā)布描述文字等),這些附屬文件的內(nèi)容也都是與源代碼相關(guān)的。換句話說,使用APT就是代替了傳統(tǒng)的對代碼信息和附屬文件的維護(hù)工作。使用過hibernate或者beehive等軟件的朋友可能深有體會。APT可以在編譯生成代碼類的同時將相關(guān)的文件寫好,比如在使用beehive時,在代碼中使用annotation聲明了許多struct要用到的配置信息,而在編譯后,這些信息會被APT以struct配置文件的方式存放。
3.如何定義processor? A.APT工作過程: 從整個過程來講,首先APT檢測在源代碼文件中哪些annotation存在。然后APT將查找我們編寫的annotation processor factories類,并且要求factories類提供處理源文件中所涉及的annotation的annotation processor。接下來,一個合適的annotation processors將被執(zhí)行,如果在processors生成源代碼文件時,該文件中含有annotation,則APT將重復(fù)上面的過程直到?jīng)]有新文件生成。
B.編寫annotation processors: 編寫一個annotation processors需要使用java1.5 lib目錄中的tools.jar提供的以下4個包: com.sun.mirror.apt: 和APT交互的接口; com.sun.mirror.declaration: 用于模式化類成員、類方法、類聲明的接口; com.sun.mirror.type: 用于模式化源代碼中類型的接口; com.sun.mirror.util: 提供了用于處理類型和聲明的一些工具。
每個processor實現(xiàn)了在com.sun.mirror.apt包中的AnnotationProcessor接口,這個接口有一個名為“process”的方法,該方法是在APT調(diào)用processor時將被用到的。一個processor可以處理一種或者多種annotation類型。 一個processor實例被其相應(yīng)的工廠返回,此工廠為AnnotationProcessorFactory接口的實現(xiàn)。APT將調(diào)用工廠類的getProcessorFor方法來獲得processor。在調(diào)用過程中,APT將提供給工廠類一個AnnotationProcessorEnvironment 類型的processor環(huán)境類對象,在這個環(huán)境對象中,processor將找到其執(zhí)行所需要的每件東西,包括對所操作的程序結(jié)構(gòu)的參考,與APT通訊并合作一同完成新文件的建立和警告/錯誤信息的傳輸。
提供工廠類有兩個方式:通過APT的“-factory”命令行參數(shù)提供,或者讓工廠類在APT的發(fā)現(xiàn)過程中被自動定位(關(guān)于發(fā)現(xiàn)過程詳細(xì)介紹請看http://java./j2se/1.5.0/docs/guide/apt/GettingStarted.html)。前者對于一個已知的factory來講是一種主動而又簡單的方式;而后者則是需要在jar文件的META-INF/services目錄中提供一個特定的發(fā)現(xiàn)路徑: 在包含factory類的jar文件中作以下的操作:在META-INF/services目錄中建立一個名為com.sun.mirror.apt.AnnotationProcessorFactory 的UTF-8編碼文件,在文件中寫入所有要使用到的factory類全名,每個類為一個單獨行。
4.一個簡單的APT實例分析: A.實例構(gòu)成: Review類:定義Review Annotation; ReviewProcessorFactory類:生成ReviewProcessor的工廠類; ReviewProcessor類:定義處理Review annotation的Processor; ReviewDeclarationVisitor類:定義Review annotation聲明訪問者,ReviewProcessor將要使用之對Class進(jìn)行訪問。 runapt.bat:定義了使用自定義的ReviewProcessor對Review類源代碼文件進(jìn)行處理的APT命令行。
B.Review類: 清單7:
package com.bjinfotech.practice.annotation.apt;
/** * 定義Review Annotation * @author cleverpig * */ public @interface Review { public static enum TypeEnum{EXCELLENT,NICE,NORMAL,BAD}; TypeEnum type(); String name() default "Review"; }
C.ReviewProcessorFactory類: 清單8:
package com.bjinfotech.practice.annotation.apt;
import java.util.Collection; import java.util.Set; import java.util.Arrays; import com.sun.mirror.apt.*; import com.sun.mirror.declaration.AnnotationTypeDeclaration; import com.sun.mirror.apt.AnnotationProcessorEnvironment; //請注意為了方便,使用了靜態(tài)import import static java.util.Collections.unmodifiableCollection; import static java.util.Collections.emptySet;
/** * 生成ReviewProcessor的工廠類 * @author cleverpig * */ public class ReviewProcessorFactory implements AnnotationProcessorFactory{ /** * 獲得針對某個(些)類型聲明定義的Processor * @param atds 類型聲明集合 * @param env processor環(huán)境 */ public AnnotationProcessor getProcessorFor( Set<AnnotationTypeDeclaration> atds, AnnotationProcessorEnvironment env){ return new ReviewProcessor(env); } /** * 定義processor所支持的annotation類型 * @return processor所支持的annotation類型的集合 */ public Collection<String> supportedAnnotationTypes(){ //“*”表示支持所有的annotation類型 //當(dāng)然也可以修改為“foo.bar.*”、“foo.bar.Baz”,來對所支持的類型進(jìn)行修飾 return unmodifiableCollection(Arrays.asList("*")); } /** * 定義processor支持的選項 * @return processor支持選項的集合 */ public Collection<String> supportedOptions(){ //返回空集合 return emptySet(); } public static void main(String[] argv){ System.out.println("ok"); } }
D.ReviewProcessor類: 清單9:
package com.bjinfotech.practice.annotation.apt;
import com.sun.mirror.apt.AnnotationProcessor; import com.sun.mirror.apt.AnnotationProcessorEnvironment; import com.sun.mirror.declaration.TypeDeclaration; import com.sun.mirror.util.DeclarationVisitors; import com.sun.mirror.util.DeclarationVisitor;
/** * 定義Review annotation的Processor * @author cleverpig * */ public class ReviewProcessor implements AnnotationProcessor{ //Processor所工作的環(huán)境 AnnotationProcessorEnvironment env=null; /** * 構(gòu)造方法 * @param env 傳入processor環(huán)境 */ public ReviewProcessor(AnnotationProcessorEnvironment env){ this.env=env; } /** * 處理方法:查詢processor環(huán)境中的類型聲明, */ public void process(){ //查詢processor環(huán)境中的類型聲明 for(TypeDeclaration type:env.getSpecifiedTypeDeclarations()){ //返回對類進(jìn)行掃描、訪問其聲明時使用的DeclarationVisitor, //傳入?yún)?shù):new ReviewDeclarationVisitor(),為掃描開始前進(jìn)行的對類聲明的處理 // DeclarationVisitors.NO_OP,表示在掃描完成時進(jìn)行的對類聲明不做任何處理 DeclarationVisitor visitor=DeclarationVisitors.getDeclarationScanner( new ReviewDeclarationVisitor(),DeclarationVisitors.NO_OP); //應(yīng)用DeclarationVisitor到類型 type.accept(visitor); } } }
E.ReviewDeclarationVisitor類: 清單10:
package com.bjinfotech.practice.annotation.apt;
import com.sun.mirror.util.*; import com.sun.mirror.declaration.*;
/** * 定義Review annotation聲明訪問者 * @author cleverpig * */ public class ReviewDeclarationVisitor extends SimpleDeclarationVisitor{ /** * 定義訪問類聲明的方法:打印類聲明的全名 * @param cd 類聲明對象 */ public void visitClassDeclaration(ClassDeclaration cd){ System.out.println("獲取Class聲明:"+cd.getQualifiedName()); } public void visitAnnotationTypeDeclaration(AnnotationTypeDeclaration atd){ System.out.println("獲取Annotation類型聲明:"+atd.getSimpleName()); } public void visitAnnotationTypeElementDeclaration(AnnotationTypeElementDeclaration aed){ System.out.println("獲取Annotation類型元素聲明:"+aed.getSimpleName()); } }
F.runapt.bat文件內(nèi)容如下: 清單11:
E: rem 項目根目錄 set PROJECT_ROOT=E:\eclipse3.1RC3\workspace\tigerFeaturePractice rem 包目錄路徑 set PACKAGEPATH=com\bjinfotech\practice\annotation\apt rem 運行根路徑 set RUN_ROOT=%PROJECT_ROOT%\build rem 源文件所在目錄路徑 set SRC_ROOT=%PROJECT_ROOT%\test rem 設(shè)置Classpath set CLASSPATH=.;%JAVA_HOME%;%JAVA_HOME%/lib/tools.jar;%RUN_ROOT%
cd %SRC_ROOT%\%PACKAGEPATH% apt -nocompile -factory com.bjinfotech.practice.annotation.apt.ReviewProcessorFactory ./*.java
四、參考資源: http://java./j2se/1.5.0/docs/guide/apt/GettingStarted.html 作者的Blog:http://blog./page/cleverpig
五、源代碼下載: [下載文件]
|