【轉】Java中Annotation(註釋)系列學習筆記(4)
(四)使用APT處理Annotation
APT(Annotation processing tool)是一種處理註釋的工具,它對源代碼文件進行檢測找出其中的Annotation,使用Annotation進行額外的處理。
Annotation處理器在處理Annotation時可以根據源文件中的Annotation生成額外的源文件和其它的文件(文件具體內容由Annotation處理器的編寫者決定),APT還會編譯生成的源文件和原來的源文件,將它們一起生成class文件.
使用APT主要的目的是簡化開發者的工作量,因爲APT可以編譯程序源代碼的同時,生成一些附屬文件(比如源文件,類文件,程序發佈描述文件等),這些附屬文件的內容也都是與源代碼相關的,換句話說,使用APT可以代替傳統的對代碼信息和附屬文件的維護工作。
如果有過Hibernate開發經驗的朋友可能知道每寫一個Java文件,還必須額外地維護一個Hibernate映射文件(一個名爲*.hbm.xml的文件,當然可以有一些工具可以自動生成),下面將使用Annotation來簡化這步操作。
爲了使用系統的apt工具來讀取源文件中的Annotation,程序員必須自定義一個Annotation處理器,編寫Annotation處理器需要使用JDK lib目錄中的tools.jar 裏的如下4個包.
com.sun.mirror.apt:和APT交互的接口
com.sun.mirror.declaration:包含各種封裝類成員,類方法,類聲明的接口。
com.sun.mirror.type:包含各種封裝源代碼中程序元素的接口。
com.sun.mirror.util:提供了用於處理類型和聲明的一些工具。
每個Annotation處理器需要實現com.sun.mirror.apt包下的AnnotationProcessor接口,這個接口中定義了一個"process"方法,該方法是由apt調用Annotation處理器時將被用到的。
一個Annotation處理器可以處理一種或多種Annotation類型。
1.通常情況下,Annotation處理器實例是由其相應的工廠返回,Annotation處理器工廠應該實現AnnotationProcessorFactory接口,APT將調用工廠類的getProcessorFor方法來獲得Annotation處理器。
2.在調用過程中,APT將提供給工廠類一個AnnotationProcessorEnvironment對象.
3.AnnotationProcessorEnvironment對象是APT工具與註釋環境通信的途徑。
使用APT工具來處理源文件時,APT首先檢測在源代碼文件中包含哪些Annotation,然後APT將查找所需的處理器工廠,並由工廠來返回相應的Annotation處理器。如果該處理器工廠支持這些Annotaion,處理器工廠返回的Annotaion處理器將會處理這些Annotation,如果生成的源文件中再次包含Annotaion,APT將會重複上面過程,直至沒有新文件生成。
爲了說明使用APT來根據源文件中的註釋來生成額外的文件,下面將定義三個Annotation類型,分別用於修飾持久化類,標識屬性和普通屬性。
程序清單
修飾表屬性
import java.lang.annotation.*;
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Persistent
{
String table();
}
修飾標識屬性
import java.lang.annotation.*;
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface IdProperty
{
String column();
String type();
String generator();
}
修飾普通成員變量的Annotation
import java.lang.annotation.*;
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Property
{
String column();
String type();
}
定義了三個Annotation之後,下面我們提供一個簡單的Java類文件,這個Java類文件使用了上面三個Annotation來修飾
@Persistent(table="persons_table")
public class Person
{
@IdProperty(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 Person()
{
}
public Person(int id , String name , int age)
{
this.id = id;
this.name = name;
this.age = age;
}
public void setId(int id)
{
this.id = id;
}
public int getId()
{
return this.id;
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
public void setAge(int age)
{
this.age = age;
}
public int getAge()
{
return this.age;
}
}
上面Person類是一個非常普通的Java類,但這個普通的Java類使用了@Persistent,@IdProperty,@IdPropery三個Annotation。下面我們爲這三個Annotation提供了一個Annotation處理器,該處理器的功能是根據註釋來生成一個Hibernate的映射文件.
程序清單
import com.sun.mirror.apt.*;
import com.sun.mirror.declaration.*;
import com.sun.mirror.type.*;
import com.sun.mirror.util.*;
import java.beans.*;
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
public class HibernateAnnotationProcessor implements AnnotationProcessor
{
//Annotation處理器環境,是該處理器與APT交互的重要途徑
private AnnotationProcessorEnvironment env;
//構造HibernateAnnotationProcessor對象時,獲得處理器環境
public HibernateAnnotationProcessor(AnnotationProcessorEnvironment env)
{
this.env = env;
}
//循環處理每個對象
public void process()
{
//遍歷每個class文件
for (TypeDeclaration t : env.getSpecifiedTypeDeclarations())
{
//定義一個文件輸出流,用於生成額外的文件
FileOutputStream fos = null;
//獲取正在處理的類名
String clazzName = t.getSimpleName();
//獲取類定義前的Persistent Annotation
Persistent per = t.getAnnotation(Persistent.class);
//當per Annotation不爲空時才繼續處理
if(per != null)
{
try
{
//創建文件輸出流
fos = new FileOutputStream(clazzName + ".hbm.xml");
PrintStream ps = new PrintStream(fos);
//執行輸出
ps.println("<?xml version="1.0"?>");
ps.println("<!DOCTYPE hibernate-mapping");
ps.println(" PUBLIC "-//Hibernate/Hibernate Mapping 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() + "">");
for (FieldDeclaration f : t.getFields())
{
//獲取指定FieldDeclaration前面的IdProperty Annotation
IdProperty id = f.getAnnotation(IdProperty.class);
//如果id Annotation不爲空
if (id != null)
{
//執行輸出
ps.println(" <id name=""
+ f.getSimpleName()
+ "" column="" + id.column()
+ "" type="" + id.type()
+ "">");
ps.println(" <generator class=""
+ id.generator() + ""/>");
ps.println(" </id>");
}
//獲取指定FieldDeclaration前面的Property Annotation
Property p = f.getAnnotation(Property.class);
//如果p Annotation不爲空
if (p != null)
{
//執行輸出
ps.println(" <property name=""
+ f.getSimpleName()
+ "" column="" + p.column()
+ "" type="" + p.type()
+ ""/>");
}
}
ps.println(" </class>");
ps.println("</hibernate-mapping>");
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
//關閉輸出流
try
{
if (fos != null)
{
fos.close();
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
}
}
}
上面的Annotation處理器比較簡單,與前面通過反射來獲取Annotation信息不同的是,這個Annotation處理器使用AnnotationProcessorEnvironment來獲取Annotation信息,AnnotationProcessorEnvironment包含了一個getSpecifiedTypeDeclarations方法,可獲取所有需要處理的類聲明,這個類聲明可包括類,接口,和枚舉等聲明,由TypeDeclaration對象表地示,與Classc對象的功能大致相似,區別只是TypeDeclaration是靜態,只要有類文件就可以獲得該對象,而Class是動態的,必須由虛擬機裝載了指定類文件後纔會產生。
TypeDeclaration又包含了如下三個常用方法來獲得對應的程序元素。
getFields:獲取該類聲明裏的所有成員變量聲明,返回值是集合元素FieldDeclaration的集合
getMethods:獲取該類聲明裏的所有成員聲明,返回值是集合元素MethodDeclaration的集合
getPackage:獲取該類聲明裏的包聲明,返回值是TypeDeclaration
上面三個方法返回的TypeDeclaration,FieldDeclaration,MethodDeclaration都可調用getAnnotation方法來訪問修飾它們的Annotation,上面程序中就是獲取不同程序元素的Annotation的代碼。
提供了上面的Annotation處理器類之後,還應該爲該Annotation處理器提供一個處理工廠,處理工廠負責決定該處理器支持哪些Annotation,並通過getProcessorFor方法來生成一個Annotation處理哭對象。
程序清單如下
import com.sun.mirror.apt.*;
import com.sun.mirror.declaration.*;
import com.sun.mirror.type.*;
import com.sun.mirror.util.*;
import java.beans.*;
import java.io.*;
import java.util.*;
public class HibernateAnnotationFactory implements AnnotationProcessorFactory
{
//所有支持的註釋類型
public Collection<String> supportedAnnotationTypes()
{
return Arrays.asList("Property" , "IdProperty" , "Persistent");
}
//返回所有支持的選項
public Collection<String> supportedOptions()
{
return Arrays.asList(new String[0]);
}
//返回Annotation處理器
public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds,AnnotationProcessorEnvironment env)
{
return new HibernateAnnotationProcessor(env);
}
}
提供了上面的處理器工廠後,就可以使用APT工具來處理上面的Person.java源文件,並根據該源文件來生成一個XML文件。 APT工具位於JDK的安裝路徑的bin路徑下。。
運行APT命令時,可以使用-factory選項來指定處理器工廠類
如下所示
rem 使用HibernateAnnotationFactory作爲處理器工廠來處理Person.java中的Annotation
apt -factory HibernateAnnotationFactory Person.java
使用APT工具,HibernateAnnotationFactory工廠來處理Person.java後,將可以看到在相同路徑下,生成了一個Person.hbm.xml文件了,該文件內容如下
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="Person" table="persons_table">
<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>
總結
通過上面生成的xml文件,我們可以看出,通過使用APT工具確實可以簡化程序開發,程序員只需把一些關鍵信息通過Annotation寫在程序中,然後使用APT工具就可生在額外的文件。