基础学习教程:Java Annotation入门
摘要:
本文针对java初学者或者annotation初次使用者全面地说明了annotation的使
用方法、定义方式、分类。初学者可以通过以上的说明制作简单的annotation程序,但是对于一些高级的annotation应用(例如使用自定义
annotation生成javabean映射xml文件)还需要进一步的研究和探讨。涉及到深入annotation的内容,作者将在后文《Java
Annotation高级应用》中谈到。
同时,annotation运行存在两种方式:运行时、编译时。上文中讨论的都是在运行时的annotation应用,但在编译时的annotation应用还没有涉及,
一、为什么使用Annotation:
在JAVA应用中,我们常遇到一些需要使用模版代码。例如,为了编写一个JAX-RPC web service,我们必须提供一对接口和实现作为模版代码。如果使用annotation对远程访问的方法代码进行修饰的话,这个模版就能够使用工具自动生成。
另外,一些API需要使用与程序代码同时维护的附属文件。例如,JavaBeans需要一个
BeanInfo
Class与一个Bean同时使用/维护,而EJB则同样需要一个部署描述符。此时在程序中使用annotation来维护这些附属文件的信息将十分便利
而且减少了错误。
二、Annotation工作方式:
在5.0版之前的Java平台已经具有了一些ad hoc
annotation机制。比如,使用transient修饰符来标识一个成员变量在序列化子系统中应被忽略。而@deprecated这个
javadoc tag也是一个ad hoc
annotation用来说明一个方法已过时。从Java5.0版发布以来,5.0平台提供了一个正式的annotation功能:允许开发者定义、使用
自己的annoatation类型。此功能由一个定义annotation类型的语法和一个描述annotation声明的语法,读取annotaion
的API,一个使用annotation修饰的class文件,一个annotation处理工具(apt)组成。
annotation并不直接影响代码语义,但是它能够工作的方式被看作类似程序的工具或者类库,它会反过来对正在运行的程序语义有所影响。annotation可以从源文件、class文件或者以在运行时反射的多种方式被读取。
当然annotation在某种程度上使javadoc tag更加完整。一般情况下,如果这个标记对java文档产生影响或者用于生成java文档的话,它应该作为一个javadoc tag;否则将作为一个annotation.
三、Annotation使用方法:
1.类型声明方式:
通常,应用程序并不是必须定义annotation类型,但是定义annotation类型并非难事。Annotation类型声明于一般的接口声明极为类似,区别只在于它在interface关键字前面使用"@"符号。
annotation类型的每个方法声明定义了一个annotation类型成员,但方法声明不必
有参数或者异常声明;方法返回值的类型被限制在以下的范围:primitives、String、Class、enums、annotation和前面类
型的数组;方法可以有默认值。
下面是一个简单的annotation类型声明:
清单1:
/**
* Describes the Request-For-Enhancement(RFE) that led
* to the presence of the annotated API element.
*/
public @interface RequestForEnhancement {
int id();
String synopsis();
String engineer() default "[unassigned]";
String date(); default "[unimplemented]";
}
代码中只定义了一个annotation类型RequestForEnhancement.
2.修饰方法的annotation声明方式:
annotation
是一种修饰符,能够如其它修饰符(如public、static、final)一般使用。习惯用法是annotaions用在其它的修饰符前面。
annotations由"@+annotation类型+带有括号的成员-值列表"组成。这些成员的值必须是编译时常量(即在运行时不变)。
A:下面是一个使用了RequestForEnhancement annotation的方法声明:
清单2:
@RequestForEnhancement(
id = 2868724,
synopsis = "Enable time-travel",
engineer = "Mr. Peabody",
date = "4/1/3007"
)
public static void travelThroughTime(Date destination) { ... }
B:当声明一个没有成员的annotation类型声明时,可使用以下方式:
清单3:
/**
* Indicates that the specification of the annotated API element
* is preliminary and subject to change.
*/
public @interface Preliminary { }
作为上面没有成员的annotation类型声明的简写方式:
清单4:
@Preliminary public class TimeTravel { ... }
C:如果在annotations中只有唯一一个成员,则该成员应命名为value:
清单5:
/**
* Associates a copyright notice with the annotated API element.
*/
public @interface Copyright {
String value();
}
更为方便的是对于具有唯一成员且成员名为value的annotation(如上文),在其使用时可以忽略掉成员名和赋值号(=):
清单6:
@Copyright("2002 Yoyodyne Propulsion Systems")
public class OscillationOverthruster { ... }
3.一个使用实例:
结合上面所讲的,我们在这里建立一个简单的基于annotation测试框架。首先我们需要一个annotation类型来表示某个方法是一个应该被测试工具运行的测试方法。
清单7:
import java.lang.annotation.*;
/**
* Indicates that the annotated method is a test method.
* This annotation should be used only on parameterless static methods.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test { }
值得注意的是annotaion类型声明是可以标注自己的,这样的annotation被称为“meta-annotations”。
在上面的代码中,@Retention(RetentionPolicy.RUNTIME)这个meta-annotation表示了此类型的
annotation将被虚拟机保留使其能够在运行时通过反射被读取。而@Target(ElementType.METHOD)表示此类型的
annotation只能用于修饰方法声明。
下面是一个简单的程序,其中部分方法被上面的annotation所标注:
清单8:
public class Foo {
@Test public static void m1() { }
public static void m2() { }
@Test public static void m3() {
throw new RuntimeException("Boom");
}
public static void m4() { }
@Test public static void m5() { }
public static void m6() { }
@Test public static void m7() {
throw new RuntimeException("Crash");
}
public static void m8() { }
}
Here is the testing tool:
import java.lang.reflect.*;
public class RunTests {
public static void main(String[] args) throws Exception {
int passed = 0, failed = 0;
for (Method m : Class.forName(args[0]).getMethods()) {
if (m.isAnnotationPresent(Test.class)) {
try {
m.invoke(null);
passed++;
} catch (Throwable ex) {
System.out.printf("Test %s failed: %s %n", m, ex.getCause());
failed++;
}
}
}
System.out.printf("Passed: %d, Failed %d%n", passed, failed);
}
}
这个程序从命令行参数中取出类名,并且遍历此类的所有方法,尝试调用其中被上面的测试annotation类型标注过的方法。在此过程中为了找出哪些方法
被annotation类型标注过,需要使用反射的方式执行此查询。如果在调用方法时抛出异常,此方法被认为已经失败,并打印一个失败报告。最后,打印运
行通过/失败的方法数量。
下面文字表示了如何运行这个基于annotation的测试工具:
清单9:
$ java RunTests Foo
Test public static void Foo.m3() failed: java.lang.RuntimeException: Boom
Test public static void Foo.m7() failed: java.lang.RuntimeException: Crash
Passed: 2, Failed 2
四、Annotation分类:
根据annotation的使用方法和用途主要分为以下几类:
1.内建Annotation――Java5.0版在java语法中经常用到的内建Annotation:
@Deprecated用于修饰已经过时的方法;
@Override用于修饰此方法覆盖了父类的方法(而非重载);
@SuppressWarnings用于通知java编译器禁止特定的编译警告。
下面代码展示了内建Annotation类型的用法:
清单10:
package com.bjinfotech.practice.annotation;
/**
* 演示如何使用java5内建的annotation
* 参考资料:
* http://java.sun.com/docs/books/tutorial/java/javaOO/annotations.html
* http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html
* http://mindprod.com/jgloss/annotations.html
* @author cleverpig
*
*/
import java.util.List;
public class UsingBuiltInAnnotation {
//食物类
class Food{}
//干草类
class Hay extends Food{}
//动物类
class Animal{
Food getFood(){
return null;
}
//使用Annotation声明Deprecated方法
@Deprecated
void deprecatedMethod(){
}
}
//马类-继承动物类
class Horse extends Animal{
//使用Annotation声明覆盖方法
@Override
Hay getFood(){
return new Hay();
}
//使用Annotation声明禁止警告
@SuppressWarnings({"deprecation","unchecked"})
void callDeprecatedMethod(List horseGroup){
Animal an=new Animal();
an.deprecatedMethod();
horseGroup.add(an);
}
}
}
2.开发者自定义Annotation:由开发者自定义Annotation类型。
下面是一个使用annotation进行方法测试的sample:
AnnotationDefineForTestFunction类型定义如下:
清单11:
package com.bjinfotech.practice.annotation;
import java.lang.annotation.*;
/**
* 定义annotation
* @author cleverpig
*
*/
//加载在VM中,在运行时进行映射
@Retention(RetentionPolicy.RUNTIME)
//限定此annotation只能标示方法
@Target(ElementType.METHOD)
public @interface AnnotationDefineForTestFunction{}
测试annotation的代码如下:
清单12:
Java Annotation 高级应用
作者:cleverpig版权声明:本文可以自由转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明作者:cleverpig(作者的Blog:
http://blog.matrix.org.cn/page/cleverpig)
原文:
http://www.matrix.org.cn/resource/article/44/44062_Java+Annotation+Apt.html关键字:java,annotation,apt
前言:前不久在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五、源代码下载:[
下载文件]
-------------------------------------------
1、引入
l 编程的一个最新趋势,尤其是 Java 编程,就是使用元数据
l 元数据可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查
l 许多元数据工具(如 Xdoclet)将这些功能添加到核心 Java 语言中,暂时成为 Java 编程功能的一部分
l Javadoc是元数据工具,但除了生成文档之外,没有固定、实用、标准化的方式将数据用于其他用途,而且HTML代码经常混入到Javadoc输出中,更进一步降低了其用于任何其它目的的价值
l JSR 175,Java编程语言的元数据工具,为将元数据合并到核心 Java 语言中提供了正式理由和说明
l Tiger 增加了Annotation的新功能,将一个更通用的元数据工具合并到核心 Java 语言中
l Annotation是可以添加到代码中的修饰符,可以用于包声明、类型声明、构造函数、方法、域变量、参数和变量
l Tiger包含内置的Annotation,还支持自己编写的定制Annotation
l 本部分将概述元数据的优点,并介绍Tiger的内置Annotation
2、元数据的价值
一般来说,元数据的好处分为三类:文档编制、编译器检查和代码分析
(1)文档编制
l 代码级文档最常被引用,但对于将元数据添加到 Java 语言中来说,文档编制可能是最不相关的理由
l 因为Javadoc已经提供了非常容易理解和健壮的方法来文档化代码
(2)编译时检查
l 元数据更重要的优点是编译器可以使用它来执行基本的编译时检查
l 具体情况请参看后面介绍的Tiger内置Annotation:@Override
(3)代码分析
l 元数据工具的最好功能就是可以使用额外数据来分析代码
l 简单的案例就是:许多时候,方法的参数类型或返回类型实际上不是该方法想要的类型;例如,参数类型可能是Object,但方法可能仅使用Integer,这在覆盖超类的方法时很容易发生;元数据可以指示代码分析工具:虽然参数类型是 Object,但 Integer 才是真正需要的
l 复杂的案例就是:即使是简单EJB系统中也具有很强的依赖性和复杂性,要具有 Home和Remote接口,以及本地的Home 和Remote接口,以及一个实现类,保持所有这些类同步非常困难;好的工具(如XDoclet)可以管理所有这些依赖性,并确保这些没有“代码级”联系,但有“逻辑级”联系的类保持同步;元数据在这里确实可以发挥它的作用
3、Annotation基础
l Annotation的格式是:@Annotation名
l 在Annotation需要数据时,通过name=value的形式提供
l 代码中可以用很多Annotation,有些Annotation会具有相同的Annotation类型
l Annotation类型和Annotation的概念类似于类和对象的概念
l Annotation有三种基本种类:
Ø 标记Annotation:只有Annotation名,不包含数据,如@MarkerAnnotation
Ø 单值Annotation:只有单一的数据,可以简化name=value的形式为value形式,如@SingleValueAnnotation("my data")
Ø 完整格式的Annotation:有多个数据成员,如@FullAnnotation(var1="data value 1", var2="data value 2", var3="data value 3")
l 可以使用花括号向Annotation变量提供值数组,如
@TODOItems({ // Curly braces indicate an array of values is being supplied
@TODO(
severity=TODO.CRITICAL,
item="Add functionality to calculate the mean of the student's grades",
assignedTo="Brett McLaughlin"
),
@TODO(
severity=TODO.IMPOTANT,
item="Print usage message to screen if no command-line flags specified",
assignedTo="Brett McLaughlin"
),
@TODO(
severity=TODO.LOW,
item="Roll a new website page with this class's new features",
assignedTo="Jason Hunter"
)
})
4、Tiger内置Annotation
(1)@Override
l @Override只用于方法,指明改方法覆盖超类中的对应方法
l 简单例子:
public class OverrideTester {
public OverrideTester() {
}
@Override public String toString() {
return super.toString() + " [Override Tester Implementation]";
}
@Override public int hashCode() {
return toString().hashCode();
}
}
l @Override可以检查输入错误导致无法覆盖超类方法的问题,例如hashCode()错误的输入为hasCode(),在编译时就会报错:
The method hasCode() of type OverrideTester must override a superclass method
l 这个便捷的小功能将帮助快速捕获打字错误
(2)@Deprecated
l 同样只用于方法,指明该方法不应该再使用了
l 简单例子:
public class DeprecatedClass {
@Deprecated public void doSomething() {
System.out.println("Deprecated method!");
// some code
}
public void doSomethingElse() {
// This method presumably does what doSomething() does, but better
}
}
l 单独编译正常通过,如果通过覆盖或调用Deprecated的方法,编译器会给出警告信息
l 注:本人在Eclipse 3.1M4环境中测试,根本不起作用(即使是改了编译参数,why?),在命令行下使用-Xlint:deprecated参数,JAVAC只给出警告信息,编译还是通过的
(3)@SuppressWarnings
l Tiger的泛型功能使得编译器对类型的安全性进行检查,特别是Java集合,如下面的例子:
public void nonGenericsMethod() {
List wordList = new ArrayList(); // no typing information on the List
wordList.add("foo"); // causes error on list addition
}
l 编译器会给出下面的警告信息:
Type safety: The method add(Object) belongs to the raw type List. References to generic
type List<E> should be parameterized
l 这对于Tiger的代码是很有帮助的,但对于JDK1.4及以前版本,不断的收到无关的警告信息是很烦人的
l 可以使用@SuppressWarnings来阻止指定类型的警告信息,如:
@SuppressWarnings(value = { "unchecked" })
public void nonGenericsMethod() {
List wordList = new ArrayList(); // no typing information on the List
wordList.add("foo"); // causes error on list addition
}
l 传递给@SuppressWarnings的类型值是一个数组,因此可以同时阻止多种类型的警告信息
l 类型值是由编译器厂商所指定的,所以上面的例子我在Eclipse 3.1M4环境和命令行中测试,都不起作用,大概是类型值没有指定对吧
---------------------------------------
1、自定义Annotation类型
(1)定义Annotation类型
l 使用@interface声明Annotation类型
public @interface InProgress {
}
l 使用Annotation类型
public class TestAnnotation {
@InProcess
public void test() {
}
}
l 如果Annotation类型和使用它的类不在相同的包中,可以import Annotation类型,以便直接使用 @InProgress
(2)添加成员
l Annotation类型可以有成员变量,以提供有用的信息
l 定义数据成员不需要定义getter和setter方法,只需要定义一个以成员名称命名的方法,并指定返回类型为需要的数据类型
l 简单的例子:
public @interface TODO {
String value();
}
l 使用带成员的Annotation类型:
public class TestAnnotation {
@InProcess
@TODO("Need to finish this method later")
public void test() {
}
}
(3)设置缺省值
l 要为Annotation类型的成员设置缺省值,需要在声明成员时使用default关键字:
public @interface GroupTODO {
public enum Severity {
CRITICAL, IMPORTANT, TRIVIAL, DOCUMENTATION
};
Severity severity() default Severity.IMPORTANT;
String item();
String assignedTo();
String dateAssigned();
}
l 当然,缺省值的类型必须与成员变量声明的类型完全相同
l 下面是使用缺省值的例子:
public class TestAnnotation {
@InProcess
@GroupTODO(
item="Need to finish this method later",
assignedTo="nelson_tu",
dateAssigned="2005/02/05"
)
public void test() {
}
}
l 下面是改写缺省值的例子:
public class TestAnnotation {
@InProcess
//@TODO("Need to finish this method later")
@GroupTODO(
severity=GroupTODO.Severity.DOCUMENTATION,
item="Need to finish this method later",
assignedTo="nelson_tu",
dateAssigned="2005/02/05"
)
public void test() {
}
}
2、元Annotation
l 元Annotation就是Annotation的Annotation,JDK5提供了4种预定义的元Annotation
(1)@Target
l @Target指定Annotation类型可以应用的程序元素,以便在其它程序元素中误用Annotation类型
l 程序元素的类型由java.lang.annotation.ElementType枚举类定义:
package java.lang.annotation;
public enum ElementType {
TYPE, // Class, interface, or enum (but not annotation)
FIELD, // Field (including enumerated values)
METHOD, // Method (does not include constructors)
PARAMETER, // Method parameter
CONSTRUCTOR, // Constructor
LOCAL_VARIABLE, // Local variable or catch clause
ANNOTATION_TYPE, // Annotation Types (meta-annotations)
PACKAGE // Java package
}
l 下面是使用@Target的例子:
@Target({ElementType.TYPE,
ElementType.METHOD,
ElementType.CONSTRUCTOR,
ElementType.ANNOTATION_TYPE})
public @interface TODO {
String value();
}
(2)@Retention
l @Retention和 Java 编译器处理Annotation类型的方式有关
l 这些方式由java.lang.annotation.RetentionPolicy
枚举类定义:
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE, // Annotation is discarded by the compiler
CLASS, // Annotation is stored in the class file, but ignored by the VM
RUNTIME // Annotation is stored in the class file and read by the VM
}
l 使用@Retention的例子参看后面的@Documented
(3)@Documented
l @Documented指明需要在Javadoc中包含Annotation(缺省是不包含的)
l 下面是一个使用@Documented的例子:
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface InProcess {
}
l 使用@Documented的一个技巧就是指定保持性策略为RetentionPolicy.RUNTIME:这样,Annotation就会保留在编译后的类文件中并且由虚拟机加载,然后Javadoc就可以抽取出Annotation,添加到类的HTML文档中
(4)@Inherited
l @Inherited最复杂、使用最少、也最容易造成混淆的一个
l 假设使用@InProgress 标记一个正在开发的类,只要正确应用@Documented,Annotation信息就会出现在Javadoc中;现在要编写一个新类,扩展那个正在开发的类,那么使用子类,或者查看它的文档,根本没法表明还有什么地方没有完成;而本来是希望@InProgress 的Annotation信息会被带到子类中,这就需要使用@Inherited了
l 下面是这样的例子:
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface InProcess {
}