前不久在matrix上先後發表了《java annotation 入門》 、《java annotation 手冊》 兩 篇文章,比較全面的對java annotation的語法、原理、使用三方面進行了闡述。由於《入門》中的簡單例程雖然簡單明瞭的說明了annotation用法,但給大家的感覺可能 是意猶未見,所以在此行文《java annotation 高級應用》,具體實例化解釋annotation和annotation processing tool(APT)的使用。望能對各位的有所幫助。
一、摘要:
《java annotation 高級應用》具體實例化解釋annotation和annotation processing tool(APT)的使用。望能對各位的有所幫助。本文列舉了用於演示annotation的BRFW演示框架、演示APT的apt代碼實例,並對其進行 較爲深度的分析,希望大家多多提意見。
二、annotation實例分析
1.BRFW(Beaninfo Runtime FrameWork)定義:
本人編寫的一個annotation功能演示框架。顧名思義,BRFW就是在運行時取得bean信息的框架。
2.BRFW的功能:
A.源代碼級annotation :在bean的源代碼中使用annotation定義bean的信息;
B.運行時獲取bean數據 :在運行時分析bean class中的annotation,並將當前bean class中field信息取出,功能類似xdoclet;
C.運行時bean數據的xml綁定 :將獲得的bean數據構造爲xml文件格式展現。熟悉j2ee的朋友知道,這個功能類似jaxb。
3.BRFW框架:
BRFW主要包含以下幾個類:
A.Persistent類 :定義了用於修飾類的固有類型成員變量的annotation。
B.Exportable類 :定義了用於修飾Class的類型的annotation。
C.ExportToXml類 :核心類,用於完成BRFW的主要功能:將具有Exportable Annotation的bean對象轉換爲xml格式文本。
D.AddressForTest類 :被A和B修飾過的用於測試目的的地址bean類。其中包含了地址定義所必需的信息:國家、省級、城市、街道、門牌等。
E.AddressListForTest類 : 被A和B修飾過的友人通訊錄bean類。其中包含了通訊錄所必備的信息:友人姓名、年齡、電話、住址(成員爲AddressForTest類型的 ArrayList)、備註。需要說明的是電話這個bean成員變量是由字符串類型組成的ArrayList類型。由於朋友的住址可能不唯一,故這裏的住 址爲由AddressForTest類型組成的ArrayList。
從上面的列表中,可以發現A、B用於修飾bean類和其類成員;C主要用於取出bean類的數據並將其作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的對象轉換爲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;
- }
- /**
- * 輸出對象的字段,當對象的字段爲Collection或者Map類型時,要調用exportObject方法繼續處理
- * @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標註過的");
- }
- //取出對象的成員變量
- Field[] fields=obj.getClass().getDeclaredFields();
- for(Field field:fields){
- //獲得成員變量的標註
- Persistent fieldAnnotation=field.getAnnotation(Persistent.class);
- if (fieldAnnotation==null){
- continue;
- }
- //重要:避免java虛擬機檢查對私有成員的訪問權限
- 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));
- }
- }
- }
- //縮進深度
- int levelDepth=0;
- //防止循環引用的檢查者,循環引用現象如:a包含b,而b又包含a
- Collection<Object> cyclicChecker=new ArrayList<Object>();
- /**
- * 返回縮進字符串
- * @return
- */
- private String getIndent(){
- String s="";
- for(int i=0;i<levelDepth;i++){
- s+="\t";
- }
- return s;
- }
- /**
- * 輸出對象,如果對象類型爲Collection和Map類型,則需要遞歸調用exportObject進行處理
- * @param obj
- * @throws Exception
- */
- public void exportObject(Object obj) throws Exception{
- Exportable exportable=null;
- String elementName=null;
- //循環引用現象處理
- 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已經被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());
- }
- //否則將對象的成員變量導出爲xml
- else{
- exportFields(obj);
- }
- levelDepth--;
- //輸出xml元素結尾
- 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類:此類的核心函數是exportObject和 exportFields方法,前者輸出對象的xml信息,後者輸出對象成員變量的信息。由於對象類型和成員類型的多樣性,所以採取了以下的邏輯:
在exportObject方法中,當對象類型爲Collection和Map類型時,則需要遞歸調用exportObject進行處理;
而如果對象類型不是Collection和Map類型的話,將判斷對象類是否被Exportable annotation修飾過:
如果沒有被修飾,則直接輸出<對象類名>對象.toString()</對象類名>作爲xml綁定結果的一部分;
如果被修飾過,則需要調用exportFields方法對對象的成員變量進行xml綁定。
在 exportFields方法中,首先取出對象的所有成員,然後獲得被Persisitent annotation修飾的成員。在其後的一句:field.setAccessible(true)是很重要的,因爲bean類定義中的成員訪問修飾都 是private,所以爲了避免java虛擬機檢查對私有成員的訪問權限,加上這一句是必需的。接着後面的語句便是輸出<成員名>成員 值</成員名>這樣的xml結構。像在exportObject方法中一般,仍然需要判斷成員類型是否爲Collection和Map類型, 如果爲上述兩種類型之一,則要在exportFields中再次調用exportObject來處理這個成員。
在main方法中,本人編寫了一段演示代碼:建立了一個由單個友人地址類(AddressForTest)組成的ArrayList作爲通訊錄類(AddressForTest)的成員的通訊錄對象,並且輸出這個對象的xml綁定,運行結果如下:
清單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?
根 據sun官方的解釋,APT(annotation processing tool)是一個命令行工具,它對源代碼文件進行檢測找出其中的annotation後,使用annotation processors來處理annotation。而annotation processors使用了一套反射API並具備對JSR175規範的支持。
annotation processors處理annotation的基本過程如下:首先,APT運行annotation processors根據提供的源文件中的annotation生成源代碼文件和其它的文件(文件具體內容由annotation processors的編寫者決定),接着APT將生成的源代碼文件和提供的源文件進行編譯生成類文件。
簡單的和前面所講的annotation 實例BRFW相比,APT就像一個在編譯時處理annotation的javac。而且從sun開發者的blog中看到,java1.6 beta版中已將APT的功能寫入到了javac中,這樣只要執行帶有特定參數的javac就能達到APT的功能。
2.爲何使用APT?
使 用APT主要目的是簡化開發者的工作量,因爲APT可以在編譯程序源代碼的同時,生成一些附屬文件(比如源文件、類文件、程序發佈描述文字等),這些附屬 文件的內容也都是與源代碼相關的。換句話說,使用APT就是代替了傳統的對代碼信息和附屬文件的維護工作。使用過hibernate或者beehive等 軟件的朋友可能深有體會。APT可以在編譯生成代碼類的同時將相關的文件寫好,比如在使用beehive時,在代碼中使用annotation聲明瞭許多 struct要用到的配置信息,而在編譯後,這些信息會被APT以struct配置文件的方式存放。
3.如何定義processor?
A.APT工作過程:
從 整個過程來講,首先APT檢測在源代碼文件中哪些annotation存在。然後APT將查找我們編寫的annotation processor factories類,並且要求factories類提供處理源文件中所涉及的annotation的annotation processor。接下來,一個合適的annotation processors將被執行,如果在processors生成源代碼文件時,該文件中含有annotation,則APT將重複上面的過程直到沒有新文 件生成。
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實現了在com.sun.mirror.apt包中的AnnotationProcessor接口,這個接口有一個名爲 “process”的方法,該方法是在APT調用processor時將被用到的。一個processor可以處理一種或者多種annotation類 型。
一個processor實例被其相應的工廠返回,此工廠爲AnnotationProcessorFactory接口的實現。APT將調用工 廠類的getProcessorFor方法來獲得processor。在調用過程中,APT將提供給工廠類一個 AnnotationProcessorEnvironment 類型的processor環境類對象,在這個環境對象中,processor將找到其執行所需要的每件東西,包括對所操作的程序結構的參考,與APT通訊 併合作一同完成新文件的建立和警告/錯誤信息的傳輸。
提供工廠類有兩個方式:通過APT的“-factory”命令行參數提供,或者讓工廠類在APT的發現過程中被自動定位(關於發現過程詳細介紹請看http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html )。前者對於一個已知的factory來講是一種主動而又簡單的方式;而後者則是需要在jar文件的META-INF/services目錄中提供一個特定的發現路徑:
在 包含factory類的jar文件中作以下的操作:在META-INF/services目錄中建立一個名爲 com.sun.mirror.apt.AnnotationProcessorFactory 的UTF-8編碼文件,在文件中寫入所有要使用到的factory類全名,每個類爲一個單獨行。
4.一個簡單的APT實例分析:
A.實例構成:
Review類:定義Review Annotation;
ReviewProcessorFactory類:生成ReviewProcessor的工廠類;
ReviewProcessor類:定義處理Review annotation的Processor;
ReviewDeclarationVisitor類:定義Review annotation聲明訪問者,ReviewProcessor將要使用之對Class進行訪問。
runapt.bat:定義了使用自定義的ReviewProcessor對Review類源代碼文件進行處理的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;
- //請注意爲了方便,使用了靜態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環境
- */
- public AnnotationProcessor getProcessorFor(
- Set<AnnotationTypeDeclaration> atds,
- AnnotationProcessorEnvironment env){
- return new ReviewProcessor(env);
- }
- /**
- * 定義processor所支持的annotation類型
- * @return processor所支持的annotation類型的集合
- */
- public Collection<String> supportedAnnotationTypes(){
- //“*”表示支持所有的annotation類型
- //當然也可以修改爲“foo.bar.*”、“foo.bar.Baz”,來對所支持的類型進行修飾
- 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所工作的環境
- AnnotationProcessorEnvironment env=null;
- /**
- * 構造方法
- * @param env 傳入processor環境
- */
- public ReviewProcessor(AnnotationProcessorEnvironment env){
- this.env=env;
- }
- /**
- * 處理方法:查詢processor環境中的類型聲明,
- */
- public void process(){
- //查詢processor環境中的類型聲明
- for(TypeDeclaration type:env.getSpecifiedTypeDeclarations()){
- //返回對類進行掃描、訪問其聲明時使用的DeclarationVisitor,
- //傳入參數:new ReviewDeclarationVisitor(),爲掃描開始前進行的對類聲明的處理
- // DeclarationVisitors.NO_OP,表示在掃描完成時進行的對類聲明不做任何處理
- DeclarationVisitor visitor=DeclarationVisitors.getDeclarationScanner(
- new ReviewDeclarationVisitor(),DeclarationVisitors.NO_OP);
- //應用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文件內容如下:
清單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 設置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.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html
作者的Blog:http://blog.matrix.org.cn/page/cleverpig
--摘自《Java Annotation 高級應用》