前言:
前不久在matrix上先后发表了《java annotation 入门》、《java annotation 手册》两篇文章,比较全面的对java annotation的语法、原理、使用三方面进行了阐述。由于《入门》中的简单例程虽然简单明了的说明了annotation用法,但给大家的感觉可能是意犹未见,所以在此行文《java annotation高级应用》,具体实例化解释annotation和annotation processing tool(APT)的使用。望能对各位的有所帮助。 中国网管论坛bbs.bitsCN.com
一、摘要: 网管bitscn_com
《java annotation高级应用》具体实例化解释annotation和annotation processing tool(APT)的使用。望能对各位的有所帮助。本文列举了用于演示annotation的BRFW演示框架、演示APT的apt代码实例,并对其进行较为深度的分析,希望大家多多提意见。
网管联盟bitsCN@com
二、annotation实例分析 网管联盟bitsCN_com
1.BRFW(Beaninfo Runtime FrameWork)定义:
网管联盟bitsCN_com
本人编写的一个annotation功能演示框架。顾名思义,BRFW就是在运行时取得bean信息的框架。
网管u家u.bitscn@com
2.BRFW的功能: 网管网www_bitscn_com
A.源代码级annotation:在bean的源代码中使用annotation定义bean的信息;
网管u家u.bitscn@com
B.运行时获取bean数据:在运行时分析bean class中的annotation,并将当前bean class中field信息取出,功能类似xdoclet;
网管bitscn_com
C.运行时bean数据的xml绑定:将获得的bean数据构造为xml文件格式展现。熟悉j2ee的朋友知道,这个功能类似jaxb.
中国网管论坛bbs.bitsCN.com
3.BRFW框架:
中国网管联盟bitsCN.com
BRFW主要包含以下几个类: 中国网管联盟bitsCN.com
A.Persistent类:定义了用于修饰类的固有类型成员变量的annotation. 中国网管论坛bbs.bitsCN.com
B.Exportable类:定义了用于修饰Class的类型的annotation.
网管bitscn_com
C.ExportToXml类:核心类,用于完成BRFW的主要功能:将具有Exportable Annotation的bean对象转换为xml格式文本。
网管u家u.bitsCN.com
D.AddressForTest类:被A和B修饰过的用于测试目的的地址bean类。其中包含了地址定义所必需的信息:国家、省级、城市、街道、门牌等。 网管u家u.bitsCN.com
E.AddressListForTest类:被A和B修饰过的友人通讯录bean类。其中包含了通讯录所必备的信息:友人姓名、年龄、电话、住址(成员为AddressForTest类型的ArrayList)、备注。需要说明的是电话这个bean成员变量是由字符串类型组成的ArrayList类型。由于朋友的住址可能不唯一,故这里的住址为由AddressForTest类型组成的ArrayList. 网管联盟bitsCN_com
从上面的列表中,可以发现A、B用于修饰bean类和其类成员;C主要用于取出bean类的数据并将其作xml绑定,代码中使用了E作为测试类;E中可能包含着多个D.
中国网管联盟bitsCN.com
在了解了这个简单框架后,我们来看一下BRFW的代码吧! 网管联盟bitsCN@com
4.BRFW源代码分析: 网管bitscn_com
A.Persistent类: 网管下载dl.bitscn.com
清单1: 网管下载dl.bitscn.com
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 ""; } 中国网管联盟bitsCN.com
B.Exportable类:
网管联盟bitsCN@com
清单2:
网管论坛bbs_bitsCN_com
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类: 网管u家u.bitsCN.com
清单3:
网管bitscn_com
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 网管联盟bitsCN_com 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;
网管网www_bitscn_com
this.doorplate=doorplate;
}
}
网管下载dl.bitscn.com
D.AddressListForTest类:
网管u家u.bitscn@com
清单4:
网管u家u.bitscn@com
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 telephone=null; 网管下载dl.bitscn.com
//友人住址:家庭、单位 @Persistent private ArrayList AddressForText=null; //备注 @Persistent private String note=null; public AddressListForTest(String name,int age, ArrayList telephoneList, ArrayList addressList, 网管联盟bitsCN_com String note){ this.friendName=name; this.age=age; this.telephone=new ArrayList(telephoneList); this.AddressForText=new ArrayList(addressList); this.note=note; } } E.ExportToXml类: 网管论坛bbs_bitsCN_com
清单5: 网管联盟bitsCN_com
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;
中国网管联盟bitsCN.com
try{
if (fieldTypeClass==String.class){
value=(String)field.get(obj);
}
else if (fieldTypeClass==int.class){
value=Integer.toString(field.getInt(obj));
网管u家u.bitscn@com }
else if (fieldTypeClass==long.class){
value=Long.toString(field.getLong(obj));
}
else if (fieldTypeClass==short.class){
value=Short.toString(field.getShort(obj));
网管下载dl.bitscn.com
}
else if (fieldTypeClass==float.class){
value=Float.toString(field.getFloat(obj));
}
else if (fieldTypeClass==double.class){
value=Double.toString(field.getDouble(obj));
网管下载dl.bitscn.com }
else if (fieldTypeClass==byte.class){
value=Byte.toString(field.getByte(obj));
}
else if (fieldTypeClass==char.class){
value=Character.toString(field.getChar(obj));
中国网管论坛bbs.bitsCN.com }
else if (fieldTypeClass==boolean.class){
value=Boolean.toString(field.getBoolean(obj));
}
}
catch(Exception ex){
ex.printStackTrace();
网管联盟bitsCN@com
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);
网管联盟bitsCN_com
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());
中国网管论坛bbs.bitsCN.com
}
}
else{
// System.out.println(obj.getClass()+"类不是使用Exportable标注过的");
}
//取出对象的成员变量
Field[] fields=obj.getClass().getDeclaredFields();
网管联盟bitsCN_com
for(Field field:fields){
//获得成员变量的标注
Persistent fieldAnnotation=field.getAnnotation(Persistent.class);
if (fieldAnnotation==null){
continue;
}
中国网管联盟bitsCN.com
//重要:避免java虚拟机检查对私有成员的访问权限
field.setAccessible(true);
Class typeClass=field.getType();
String name=field.getName();
String value=getFieldValue(field,typeClass,obj);
网管网www_bitscn_com
//如果获得成员变量的值,则输出
if (value!=null){
System.out.println(getIndent()+"<"+name+">\n"
+getIndent()+"\t"+value+"\n"+getIndent()+""+NAME+">");
}
网管联盟bitsCN@com
//处理成员变量中类型为Collection或Map
else if ((field.get(obj) instanceof Collection)||
(field.get(obj) instanceof Map)){
exportObject(field.get(obj));
}
网管联盟bitsCN_com else{
exportObject(field.get(obj));
}
}
}
//缩进深度
int levelDepth=0;
//防止循环引用的检查者,循环引用现象如:a包含b,而b又包含a
网管联盟bitsCN@com Collection
cyclicChecker=new ArrayList
(); /** * 返回缩进字符串 * @return */ private String getIndent(){ String s=""; for(int i=0;i s+="\t"; } return s; 网管u家u.bitscn@com } /** * 输出对象,如果对象类型为Collection和Map类型,则需要递归调用exportObject进行处理 * @param obj * @throws Exception */ public void exportObject(Object obj) throws Exception{ Exportable exportable=null; String elementName=null; //循环引用现象处理 if (cyclicChecker.contains(obj)){ 网管联盟bitsCN@com return; } cyclicChecker.add(obj); //首先处理Collection和Map类型 if (obj instanceof Collection){ for(Iterator i=((Collection)obj).iterator();i.hasNext();){ 网管u家u.bitscn@com
exportObject(i.next()); } } else if (obj instanceof Map){ for(Iterator i=((Map)obj).keySet().iterator();i.hasNext();){ exportObject(i.next()); 中国网管联盟bitsCN.com } } else{ exportable=obj.getClass().getAnnotation(Exportable.class); //如果obj已经被Exportable Annotation修饰过了(注意annotation是具有继承性的), //则使用其name作为输出xml的元素name if (exportable!=null){ 网管网www_bitscn_com if (exportable.value().length()>0){ elementName=exportable.value(); } else{ elementName=exportable.name(); 网管论坛bbs_bitsCN_com } } //未被修饰或者Exportable Annotation的值为空字符串, //则使用类名作为输出xml的元素name if (exportable==null||elementName.length()==0){ elementName=obj.getClass().getSimpleName(); 网管网www_bitscn_com } //输出xml元素头 System.out.println(getIndent()+"<"+elementName+">"); levelDepth++; //如果没有被修饰,则直接输出其toString()作为元素值 if (exportable==null){ 网管联盟bitsCN@com System.out.println(getIndent()+obj.toString()); } //否则将对象的成员变量导出为xml else{ exportFields(obj); } 网管网www.bitscn.com
levelDepth--; //输出xml元素结尾 System.out.println(getIndent()+""+elementName+">"); } cyclicChecker.remove(obj); } public static void main(String[] argv){ 网管联盟bitsCN_com try{ AddressForTest ad=new AddressForTest("China","Beijing", "Beijing","winnerStreet","10"); ExportToXml test=new ExportToXml(); 网管联盟bitsCN@com
ArrayList telephoneList=new ArrayList(); telephoneList.add("66608888"); telephoneList.add("66608889"); ArrayList adList=new ArrayList(); adList.add(ad); 网管网www_bitscn_com AddressListForTest adl=new AddressListForTest("coolBoy", 18,telephoneList,adList,"some words"); test.exportObject(adl); } 网管u家u.bitsCN.com catch(Exception ex){ ex.printStackTrace(); } } } 在ExportToXml类之前的类比较简单,这里必须说明一下ExportToXml类:此类的核心函数是exportObject和exportFields方法,前者输出对象的xml信息,后者输出对象成员变量的信息。由于对象类型和成员类型的多样性,所以采取了以下的逻辑: 网管联盟bitsCN_com
在exportObject方法中,当对象类型为Collection和Map类型时,则需要递归调用exportObject进行处理; 网管下载dl.bitscn.com
而如果对象类型不是Collection和Map类型的话,将判断对象类是否被Exportable annotation修饰过:
网管联盟bitsCN_com
如果没有被修饰,则直接输出<对象类名>对象。toString()对象类名>作为xml绑定结果的一部分;
网管联盟bitsCN_com
如果被修饰过,则需要调用exportFields方法对对象的成员变量进行xml绑定。 网管u家u.bitscn@com
在exportFields方法中,首先取出对象的所有成员,然后获得被Persisitent annotation修饰的成员。在其后的一句:field.setAccessible(true)是很重要的,因为bean类定义中的成员访问修饰都是private,所以为了避免java虚拟机检查对私有成员的访问权限,加上这一句是必需的。接着后面的语句便是输出<成员名>成员值成员名>这样的xml结构。像在exportObject方法中一般,仍然需要判断成员类型是否为Collection和Map类型,如果为上述两种类型之一,则要在exportFields中再次调用exportObject来处理这个成员。
网管下载dl.bitscn.com
在main方法中,本人编写了一段演示代码:建立了一个由单个友人地址类(AddressForTest)组成的ArrayList作为通讯录类(AddressForTest)的成员的通讯录对象,并且输出这个对象的xml绑定,运行结果如下:
网管u家u.bitscn@com
清单6:
网管bitscn_com
coolBoy 18 66608888 66608889 网管bitscn_com China Beijing 网管bitscn_com Beijing winnerStreet 10 中国网管联盟bitsCN.com some words 三、APT实例分析: 网管下载dl.bitscn.com
1.何谓APT?
网管bitscn_com
根据sun官方的解释,APT(annotation processing tool)是一个命令行工具,它对源代码文件进行检测找出其中的annotation后,使用annotation processors来处理annotation.而annotation processors使用了一套反射API并具备对JSR175规范的支持。 网管u家u.bitscn@com
annotation processors处理annotation的基本过程如下:首先,APT运行annotation processors根据提供的源文件中的annotation生成源代码文件和其它的文件(文件具体内容由annotation processors的编写者决定),接着APT将生成的源代码文件和提供的源文件进行编译生成类文件。 网管下载dl.bitscn.com
简单的和前面所讲的annotation实例BRFW相比,APT就像一个在编译时处理annotation的javac.而且从sun开发者的blog中看到,java1.6 beta版中已将APT的功能写入到了javac中,这样只要执行带有特定参数的javac就能达到APT的功能。
网管网www_bitscn_com
2.为何使用APT?
网管网www.bitscn.com
使用APT主要目的是简化开发者的工作量,因为APT可以在编译程序源代码的同时,生成一些附属文件(比如源文件、类文件、程序发布描述文字等),这些附属文件的内容也都是与源代码相关的。换句话说,使用APT就是代替了传统的对代码信息和附属文件的维护工作。使用过hibernate或者beehive等软件的朋友可能深有体会。APT可以在编译生成代码类的同时将相关的文件写好,比如在使用beehive时,在代码中使用annotation声明了许多struct要用到的配置信息,而在编译后,这些信息会被APT以struct配置文件的方式存放。 中国网管联盟bitsCN.com
3.如何定义processor? 网管联盟bitsCN@com
A.APT工作过程:
网管联盟bitsCN_com
从整个过程来讲,首先APT检测在源代码文件中哪些annotation存在。然后APT将查找我们编写的annotation processor factories类,并且要求factories类提供处理源文件中所涉及的annotation的annotation processor.接下来,一个合适的annotation processors将被执行,如果在processors生成源代码文件时,该文件中含有annotation,则APT将重复上面的过程直到没有新文件生成。 网管论坛bbs_bitsCN_com
B.编写annotation processors: 网管bitscn_com
编写一个annotation processors需要使用java1.5 lib目录中的tools.jar提供的以下4个包:
中国网管论坛bbs.bitsCN.com
com.sun.mirror.apt: 和APT交互的接口;
网管下载dl.bitscn.com
com.sun.mirror.declaration: 用于模式化类成员、类方法、类声明的接口; 网管论坛bbs_bitsCN_com
com.sun.mirror.type: 用于模式化源代码中类型的接口; 网管网www_bitscn_com
com.sun.mirror.util: 提供了用于处理类型和声明的一些工具。 网管u家u.bitscn@com
每个processor实现了在com.sun.mirror.apt包中的AnnotationProcessor接口,这个接口有一个名为“process”的方法,该方法是在APT调用processor时将被用到的。一个processor可以处理一种或者多种annotation类型。
网管下载dl.bitscn.com
一个processor实例被其相应的工厂返回,此工厂为AnnotationProcessorFactory接口的实现。APT将调用工厂类的getProcessorFor方法来获得processor.在调用过程中,APT将提供给工厂类一个AnnotationProcessorEnvironment 类型的processor环境类对象,在这个环境对象中,processor将找到其执行所需要的每件东西,包括对所操作的程序结构的参考,与APT通讯并合作一同完成新文件的建立和警告/错误信息的传输。
网管网www.bitscn.com
提供工厂类有两个方式:通过APT的“-factory”命令行参数提供,或者让工厂类在APT的发现过程中被自动定位(关于发现过程详细介绍请看http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html )。前者对于一个已知的factory来讲是一种主动而又简单的方式;而后者则是需要在jar文件的META-INF/services目录中提供一个特定的发现路径:
网管网www_bitscn_com
在包含factory类的jar文件中作以下的操作:在META-INF/services目录中建立一个名为com.sun.mirror.apt.AnnotationProcessorFactory 的UTF-8编码文件,在文件中写入所有要使用到的factory类全名,每个类为一个单独行。 网管u家u.bitscn@com
4.一个简单的APT实例分析:
网管论坛bbs_bitsCN_com
A.实例构成:
网管u家u.bitsCN.com
Review类:定义Review Annotation;
网管u家u.bitscn@com
ReviewProcessorFactory类:生成ReviewProcessor的工厂类; 网管论坛bbs_bitsCN_com
ReviewProcessor类:定义处理Review annotation的Processor; 网管论坛bbs_bitsCN_com
ReviewDeclarationVisitor类:定义Review annotation声明访问者,ReviewProcessor将要使用之对Class进行访问。
网管u家u.bitsCN.com
runapt.bat:定义了使用自定义的ReviewProcessor对Review类源代码文件进行处理的APT命令行。 网管网www.bitscn.com
B.Review类: 网管u家u.bitscn@com
清单7:
网管论坛bbs_bitsCN_com
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"; } 网管bitscn_com
C.ReviewProcessorFactory类:
网管bitscn_com
清单8: 网管网www_bitscn_com
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( 网管u家u.bitsCN.com Set atds, AnnotationProcessorEnvironment env){ return new ReviewProcessor(env); } /** * 定义processor所支持的annotation类型 * @return processor所支持的annotation类型的集合 */ public Collection supportedAnnotationTypes(){ //“*”表示支持所有的annotation类型 网管论坛bbs_bitsCN_com
//当然也可以修改为“foo.bar.*”、“foo.bar.Baz”,来对所支持的类型进行修饰 return unmodifiableCollection(Arrays.asList("*")); } /** * 定义processor支持的选项 * @return processor支持选项的集合 */ public Collection supportedOptions(){ //返回空集合 return emptySet(); } 网管网www.bitscn.com public static void main(String[] argv){ System.out.println("ok"); } } 网管论坛bbs_bitsCN_com
D.ReviewProcessor类: 网管bitscn_com
清单9: 网管联盟bitsCN@com
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){ 网管u家u.bitsCN.com this.env=env; } /** * 处理方法:查询processor环境中的类型声明, */ public void process(){ //查询processor环境中的类型声明 for(TypeDeclaration type:env.getSpecifiedTypeDeclarations()){ //返回对类进行扫描、访问其声明时使用的DeclarationVisitor, //传入参数:new ReviewDeclarationVisitor(),为扫描开始前进行的对类声明的处理 网管下载dl.bitscn.com // DeclarationVisitors.NO_OP,表示在扫描完成时进行的对类声明不做任何处理 DeclarationVisitor visitor=DeclarationVisitors.getDeclarationScanner( new ReviewDeclarationVisitor(),DeclarationVisitors.NO_OP); //应用DeclarationVisitor到类型 type.accept(visitor); 中国网管联盟bitsCN.com } } } E.ReviewDeclarationVisitor类:
网管网www_bitscn_com
清单10:
网管u家u.bitsCN.com
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){ 中国网管联盟bitsCN.com System.out.println("获取Annotation类型声明:"+atd.getSimpleName()); } public void visitAnnotationTypeElementDeclaration(AnnotationTypeElementDeclaration aed){ System.out.println("获取Annotation类型元素声明:"+aed.getSimpleName()); } } F.runapt.bat文件内容如下:
网管联盟bitsCN@com
清单11:
网管联盟bitsCN@com
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 网管网www_bitscn_com
网管bitscn_com