dyerac  
dyerac In Java
公告

日历
<2007年7月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234
统计
  • 随笔 - 36
  • 文章 - 10
  • 评论 - 94
  • 引用 - 0

导航

常用链接

留言簿(5)

随笔分类(49)

随笔档案(36)

文章分类(11)

文章档案(10)

相册

dyerac

搜索

  •  

积分与排名

  • 积分 - 78700
  • 排名 - 706

最新随笔

最新评论

阅读排行榜

评论排行榜

 

基础学习教程: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 175Java编程语言的元数据工具,为将元数据合并到核心 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系统中也具有很强的依赖性和复杂性,要具有 HomeRemote接口,以及本地的Home Remote接口,以及一个实现类,保持所有这些类同步非常困难;好的工具(如XDoclet)可以管理所有这些依赖性,并确保这些没有“代码级”联系,但有“逻辑级”联系的类保持同步;元数据在这里确实可以发挥它的作用

 

3Annotation基础

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"
  )
})

 

4Tiger内置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         定义数据成员不需要定义gettersetter方法,只需要定义一个以成员名称命名的方法,并指定返回类型为需要的数据类型

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就是AnnotationAnnotationJDK5提供了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 标记一个正在开发的类,只要正确应用@DocumentedAnnotation信息就会出现在Javadoc中;现在要编写一个新类,扩展那个正在开发的类,那么使用子类,或者查看它的文档,根本没法表明还有什么地方没有完成;而本来是希望@InProgress Annotation信息会被带到子类中,这就需要使用@Inherited

l         下面是这样的例子:

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface InProcess {
 
}

 



posted on 2007-07-22 22:42 dyerac in java... 阅读(822) 评论(1)  编辑  收藏 所属分类: JavaSE
评论:
  • # 《中国科技博览》征稿函  刘越 Posted @ 2008-10-24 09:47

    《中国科技博览》征稿函

    中国核心期刊(遴选)数据库收录期刊   中国学术期刊中国(光盘版)收录期刊
    统一刊号: CN11-4450/T ISSN1009-914X
    (国家级学术期刊 科技论文交流平台)
    《中国科技博览》杂志是由国务院国有资产监督管理委员会(国资委)主管,中国包装总公司主办的国家级学术期刊;。(国内刊号: CN11-4450/T 国际刊号: ISSN1009-914X )本刊被《中国核心期刊(遴选)数据库》《中国学术期刊(光盘版)》《万方数据数字化期刊群》《中文科技期刊数据库》等网络媒体全文收录,国内外深有影响。旨在推广科教新理论、新经验,为广大教育、科研工作者提供学术交流平台,推动科技、教育事业发展。
    【办刊宗旨】
    本刊坚持国家科技创新、教育创新方针,传播科技理念、报道科技动态、倡导科技创新、促进科技进步;广泛探讨交流科技、教育工作成果;多方位、多层次介绍科教创新的前沿性内容。
    【主要栏目】
    工程技术、工业技术、教学研究、建筑科学、IT技术、电子商务、资源与环境、财会审计、信息科学、学术论坛、行业科技、创新与实践、经营管理、经济综述、图书馆论坛等相关栏目及内容.
    【征稿对象】
    全国教科研工作者、广大教师、各级科技与教育管理部门领导、科研院所、科技教育人员、科教领域管理人员、各大院校的在读博士生、硕士生及社会各界关心科教工作的人士。
    【来稿须知】
    ① 稿件应具有科学性、先进性和实用性,论点明确、论据可靠、数据准确、逻辑严谨、文字通顺。
    ② 论文2200—6000字符数为宜,来稿请使用word排版,并请注明作者姓名、单位、通讯地址、邮编、电子信箱、联系电话等,本刊欢迎网上投稿。本刊已被《中国核心期刊(遴选)数据
    ③ 论文的基本要素齐全,文章标题、作者单位、作者姓名、关键词、摘要、结语、参考文献等。
    ④ 所投稿件请保证文章版权的独立性,无抄袭、署名排序无争议、文责自负,请勿一稿多投!
    *来稿无论我刊选用与否,都会在5个工作日内将审理结果尽快通知作者,作者也可来电查询,以免影响正常发表。
    投稿信箱: zgkjblliu@yahoo.com.cn
    【本刊诚聘特约编委、组稿编辑】
    为不断提高刊物学术水平,我刊现面向全国教育科研系统招聘编委及组稿编辑,负责部分重点稿件的编审并帮助所在单位、系统、地区的教科研工作者解决学术论文的写作和在本刊的发表事宜。
    权利待遇:发特约编委证书;优先在本刊刊发学术论文;本刊将会酌情付给酬金。
    详情请来电来函咨询:010-51347973
    E-mail:zgkjblliu@yahoo.com.cn
    【有关事宜】
    优秀稿件我们会提交参选我社论文评优活动,获奖作品颁布发荣誉证书。
    【网上查证】
    登陆以下官方网站可对《中国科技博览》进行网上查询。
    北京市新闻出版局:www.bjppb.gov.cn
    中国新闻总署: www.gapp.gov.cn/
    联 系 人:刘越(老师)
    电 话:010--51347973 13041201895
    投稿信箱: zgkjblliu@yahoo.com.cn (投稿专用)
    工 作Q Q: 793021885
    杂志社地址:北京市东城区兴化路9号院大楼516室
    邮 编:100013


      回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 
 
Copyright © dyerac in java... Powered by: 博客园 模板提供:沪江博客