您的位置:首页 > 编程语言 > Java开发

Java Annotation 高级应用

2015-01-09 15:39 519 查看
前言:

前不久在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:

Java代码


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:

Java代码


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:

Java代码


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:

Java代码


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:

Java代码


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:

Java代码


<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:

Java代码


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:

Java代码


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:

Java代码


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:

Java代码


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:

Java代码


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=.;%J***A_HOME%;%J***A_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 高级应用》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: