深入理解Java注解(2):高级应用

http://blog.csdn.net/zhoudaxia/article/details/33731583

一、为什么使用Annotation:


  在JAVA应用中,我们常遇到一些需要使用模版代码。例如,为了编写一个JAX-RPC web service,我们必须提供一对接口和实现作为模版代码。如果使用annotation对远程访问的方法代码进行修饰的话,这个模版就能够使用工具自动生成。
  另外,一些API需要使用与程序代码同时维护的附属文件。例如,JavaBeans需要一个BeanInfo Class与一个Bean同时使用/维护,而EJB则同样需要一个部署描述符。此时在程序中使用annotation来维护这些附属文件的信息将十分便利而且减少了错误。

二、Annotation工作方式:

  在5.0版之前的Java平台已经具有了一些ad hocannotation机制。比如,使用transient修饰符来标识一个成员变量在序列化子系统中应被忽略。而@deprecated这个javadoc tag也是一个ad hocannotation用来说明一个方法已过时。从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:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * Describes the Request-For-Enhancement(RFE) that led 
  3.  * to the presence of the annotated API element. 
  4.  */  
  5. public @interface RequestForEnhancement {  
  6.     int    id();  
  7.     String synopsis();  
  8.     String engineer() default "[unassigned]";   
  9.     String date();    default "[unimplemented]";   
  10. }  

  代码中只定义了一个annotation类型RequestForEnhancement。
  2、修饰方法的annotation声明方式:
  annotation是一种修饰符,能够如其它修饰符(如public、static、final)一般使用。习惯用法是annotaions用在其它的修饰符前面。annotations由“@+annotation类型+带有括号的成员-值列表”组成。这些成员的值必须是编译时常量(即在运行时不变)。
  A:下面是一个使用了RequestForEnhancement annotation的方法声明:
清单2:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @RequestForEnhancement(  
  2.     id       = 2868724,  
  3.     synopsis = "Enable time-travel",  
  4.     engineer = "Mr. Peabody",  
  5.     date     = "4/1/3007"  
  6. )  
  7. public static void travelThroughTime(Date destination) { ... }  
  B:当声明一个没有成员的annotation类型声明时,可使用以下方式:
清单3:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * Indicates that the specification of the annotated API element 
  3.  * is preliminary and subject to change. 
  4.  */  
  5. public @interface Preliminary { }  
  作为上面没有成员的annotation类型声明的简写方式:
清单4:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @Preliminary public class TimeTravel { ... }  
  C: 如果在annotations中只有唯一一个成员,则该成员应命名为value:
清单5:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * Associates a copyright notice with the annotated API element. 
  3.  */  
  4. public @interface Copyright {  
  5.     String value();  
  6. }  
  更为方便的是对于具有唯一成员且成员名为value的annotation(如上文),在其使用时可以忽略掉成员名和赋值号(=):
清单6:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @Copyright("2002 Yoyodyne Propulsion Systems")  
  2. public class OscillationOverthruster { ... }  
   D:下面是一个复杂的Annotataion类型声明,其成员是Annotation类型的数组
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. import java.lang.annotation.*;  
  2.           
  3. /** 
  4.  * Reviews annotation类型只有一个成员, 
  5.  * 由Review annotation组成的数组 
  6.  */  
  7. @Retention(RetentionPolicy.RUNTIME)  
  8. public @interface Reviews {  
  9.     Review[] value();  
  10. }  
  11.   
  12. /** 
  13.  * Review annotation类型有3个成员:  
  14.  * 枚举类型成员grade 
  15.  * 表示Review名称的字符串类型成员Reviewer 
  16.  * 具有默认值的字符串类型成员Comment。 
  17.  */  
  18. public @interface Review {  
  19.     // 内嵌的枚举类型  
  20.     public static enum Grade { EXCELLENT, SATISFACTORY, UNSATISFACTORY };  
  21.   
  22.     // 下面的方法定义了annotation的成员  
  23.     Grade grade();                  
  24.     String reviewer();            
  25.     String comment() default "";    
  26. }  
  Reviews annotation类型只有一个成员,但是这个成员的类型是复杂的:由Review annotation组成的数组。Review annotation类型有3个成员:枚举类型成员grade、表示Review名称的字符串类型成员Reviewer、具有默认值的字符串类型成员Comment。
   Annotation类型的成员不能是generic。只有返回值类型是Class的方法可以在annotation类型中使用generic,因为此方法能够用类转换将各种类型转换为Class。

  最后,我们来定义一个annotation方法用于罗列出类运行中所有的unchecked异常(这种情况不一定是错误)。这个annotation类型将一个数组作为了唯一的成员。数组中的每个元素都是异常类。为了加强对未检查的异常(此类异常都是在运行时抛出)进行报告,我们可以在代码中对异常的类型进行限制:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public @interface UncheckedExceptions {  
  2.     Class<? extends RuntimeException>[] value();  
  3. }  
  3、一个使用实例:
  结合上面所讲的,我们在这里建立一个简单的基于annotation测试框架。首先我们需要一个annotation类型来表示某个方法是一个应该被测试工具运行的测试方法。
清单7:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. import java.lang.annotation.*;  
  2.   
  3. /** 
  4.  * Indicates that the annotated method is a test method. 
  5.  * This annotation should be used only on parameterless static methods. 
  6.  */  
  7. @Retention(RetentionPolicy.RUNTIME)  
  8. @Target(ElementType.METHOD)  
  9. public @interface Test { }  
  值得注意的是annotaion类型声明是可以标注自己的,这样的annotation被称为“meta-annotations”。
  在上面的代码中,@Retention(RetentionPolicy.RUNTIME)这个meta-annotation表示了此类型的annotation将被虚拟机保留使其能够在运行时通过反射被读取。而@Target(ElementType.METHOD)表示此类型的annotation只能用于修饰方法声明。
  下面是一个简单的程序,其中部分方法被上面的annotation所标注:
清单8:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class Foo {  
  2.     @Test public static void m1() { }  
  3.       
  4.     public static void m2() { }  
  5.       
  6.     @Test public static void m3() {  
  7.         throw new RuntimeException("Boom");  
  8.     }  
  9.       
  10.     public static void m4() { }  
  11.       
  12.     @Test public static void m5() { }  
  13.       
  14.     public static void m6() { }  
  15.       
  16.     @Test public static void m7() {  
  17.         throw new RuntimeException("Crash");  
  18.     }  
  19.       
  20.     public static void m8() { }  
  21. }  
  使用测试:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. import java.lang.reflect.*;  
  2.   
  3. public class RunTests {  
  4.    public static void main(String[] args) throws Exception {  
  5.       int passed = 0, failed = 0;  
  6.       for (Method m : Class.forName(args[0]).getMethods()) {  
  7.          if (m.isAnnotationPresent(Test.class)) {  
  8.             try {  
  9.                m.invoke(null);  
  10.                passed++;  
  11.             } catch (Throwable ex) {  
  12.                System.out.printf("Test %s failed: %s %n", m, ex.getCause());  
  13.                failed++;  
  14.             }  
  15.          }  
  16.       }  
  17.       System.out.printf("Passed: %d, Failed %d%n", passed, failed);  
  18.    }  
  19. }  
  这个程序从命令行参数中取出类名,并且遍历此类的所有方法,尝试调用其中被上面的测试annotation类型标注过的方法。在此过程中为了找出哪些方法被annotation类型标注过,需要使用反射的方式执行此查询。如果在调用方法时抛出异常,此方法被认为已经失败,并打印一个失败报告。最后,打印运行通过/失败的方法数量。
  下面文字表示了如何运行这个基于annotation的测试工具:
清单9:
[plain]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. $ java RunTests Foo  
  2. Test public static void Foo.m3() failed: java.lang.RuntimeException: Boom   
  3. Test public static void Foo.m7() failed: java.lang.RuntimeException: Crash   
  4. Passed: 2, Failed 2  

  注解在很多框架、类库中有广泛的应用。如JUnit测试框架, springhibernate, EJB等等。这也是开发人员所常常用到的一种方式。

  上面的介绍说明了annotation的使用方法、定义方式、分类。初学者可以通过以上的说明制作简单的annotation程序,但是对于一些高级的annotation应用(例如使用自定义annotation生成javabean映射xml文件)还需要进一步的研究和探讨。同时,annotation运行存在两种方式:运行时、编译时。上文中讨论的都是在运行时的annotation应用,但在编译时的annotation应用还没有涉及,因为编译时的annotation要使用annotation processing tool(APT)
  annotation本身使用时十分简便。例如一个本地变量可以被一个以NonNull命名的annotation类型所标注,来作为对这个本地变量不能被赋予null值的断言。而我们可以编写与之配套的一个annotation代码分析工具,使用它来对具有前面变量的代码进行解析,并且尝试验证这个断言。当然这些代码并不必自己编写。在JDK安装后,在JDK/bin目录中可以找到名为“apt”的工具,它提供了处理annotation的框架:它启动后扫描源代码中的annotation,并调用我们定义好的annotation处理器完成我们所要完成的工作(比如验证前面例子中的断言)。说到这里,annotation的强大功能似乎可以替代XDoclet这类的工具了,随着我们的深入,大家会更加坚信这一点。
  注:详细描述请参看JSR 250规范 http://www.jcp.org/aboutJava/communityprocess/pfd/jsr250/


四、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的朋友知道,这个功能类似JAXP。
  3.BRFW框架:
  BRFW主要包含以下几个类:
  A.Persistent类:定义了用于修饰类的固有成员变量的annotation。
  B.Exportable类:定义了用于修饰Class的类型的annotation。
  C.ExportToXml类:核心类,用于完成BRFW的主要功能:将具有Exportable Annotation的bean对象转换为xml格式文本。
  D.AddressForTest类:被A和B修饰过的用于测试目的的地址bean类。其中包含了地址定义所必需的信息:国家、省级、城市、街道、门牌等。
  E.ContactBookForTest类:被A和B修饰过的友人通讯录bean类。其中包含了通讯录所必备的信息:友人姓名、年龄、电话、住址(成员为AddressForTest类型的ArrayList)、备注。需要说明的是电话这个bean成员变量是由字符串类型组成的ArrayList类型。由于朋友的住址可能不唯一,故这里的住址为由AddressForTest类型组成的ArrayList。
  从上面的列表中,可以发现A、B用于修饰bean类和其类成员;C主要用于取出bean类的数据并将其作xml绑定,代码中使用了E作为测试类;E中可能包含着多个D。
在了解了这个简单框架后,我们来看一下BRFW的代码吧!
  4.BRFW源代码分析:
  A.Persistent类:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package beaninfoframework;  
  2.   
  3. import java.lang.annotation.*;  
  4.   
  5. /** 
  6.  * 用于修饰类的成员变量的annotation 
  7.  * @author jackzhou 
  8.  */  
  9. @Retention(RetentionPolicy.RUNTIME)  
  10. @Target(ElementType.FIELD)  
  11. public @interface Persistent {  
  12.     String value() default "";  
  13. }  
   B.Exportable类:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package beaninfoframework;  
  2.   
  3. import java.lang.annotation.*;  
  4.   
  5. /** 
  6.  * 用于修饰类类型的annotation 
  7.  * @author jackzhou 
  8.  */  
  9. @Retention(RetentionPolicy.RUNTIME)  
  10. @Target(ElementType.TYPE)  
  11. public @interface Exportable {  
  12.     // 名称  
  13.     String name() default "";  
  14.     // 描述  
  15.     String description() default "";  
  16.     // 省略name和description后,用来保存name值  
  17.     String value() default "";  
  18. }  
   C.AddressForTest类:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package beaninfoframework;  
  2.   
  3. /** 
  4.  * 用于测试的地址类 
  5.  * @author jackzhou 
  6.  */  
  7. @Exportable("address")  
  8. public class AddressForTest {  
  9.   
  10.     //国家  
  11.   
  12.     @Persistent  
  13.     private String country = null;  
  14.   
  15.     //省级  
  16.     @Persistent  
  17.     private String province = null;  
  18.   
  19.     //城市  
  20.     @Persistent  
  21.     private String city = null;  
  22.   
  23.     //街道  
  24.     @Persistent  
  25.     private String street = null;  
  26.   
  27.     //门牌  
  28.     @Persistent  
  29.     private String doorplate = null;  
  30.   
  31.     public AddressForTest(String country, String province,  
  32.             String city, String street, String doorplate) {  
  33.         this.country = country;  
  34.         this.province = province;  
  35.         this.city = city;  
  36.         this.street = street;  
  37.         this.doorplate = doorplate;  
  38.     }  
  39. }  
   D.ContactBookForTest类:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package beaninfoframework;  
  2.   
  3. import java.util.ArrayList;  
  4.   
  5. /** 
  6.  * 用于测试的友人通讯录类 
  7.  * 包含:姓名、年龄、电话、住址(多个)、备注 
  8.  * @author jackzhou 
  9.  */  
  10. @Exportable(name = "contactbook", description = "contact book")  
  11. public class ContactBookForTest {  
  12.   
  13.     //友人姓名  
  14.     @Persistent  
  15.     private String friendName = null;  
  16.   
  17.     //友人年龄  
  18.     @Persistent  
  19.     private int age = 0;  
  20.   
  21.     //友人电话  
  22.     @Persistent  
  23.     private ArrayList<String> telephones = null;  
  24.   
  25.     //友人住址:家庭、单位  
  26.     @Persistent  
  27.     private ArrayList<AddressForTest> AddressForText = null;  
  28.   
  29.     //备注  
  30.     @Persistent  
  31.     private String note = null;  
  32.   
  33.     public ContactBookForTest(String name, int age,  
  34.             ArrayList<String> telephoneList,  
  35.             ArrayList<AddressForTest> addressList,  
  36.             String note) {  
  37.         this.friendName = name;  
  38.         this.age = age;  
  39.         this.telephones = new ArrayList<>(telephoneList);  
  40.         this.AddressForText = new ArrayList<>(addressList);  
  41.         this.note = note;  
  42.     }  
  43. }  
   E.ExportToXml类:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package beaninfoframework;  
  2.   
  3. import java.lang.reflect.Field;  
  4. import java.util.ArrayList;  
  5. import java.util.Collection;  
  6. import java.util.Iterator;  
  7. import java.util.Map;  
  8.   
  9. /** 
  10.  * 将具有Exportable Annotation的对象转换为xml格式文本 
  11.  * @author jackzhou 
  12.  */  
  13. public class ExportToXml {  
  14.   
  15.     /** 
  16.      * 返回对象的成员变量的值(字符串类型) 
  17.      * @param field 对象的成员变量 
  18.      * @param fieldTypeClass 对象的类型 
  19.      * @param obj 对象 
  20.      * @return 对象的成员变量的值(字符串类型) 
  21.      */  
  22.     private String getFieldValue(Field field, Class fieldTypeClass, Object obj) {  
  23.         String value = null;  
  24.         try {  
  25.             if (fieldTypeClass == String.class) {  
  26.                 value = (String) field.get(obj);  
  27.             } else if (fieldTypeClass == int.class) {  
  28.                 value = Integer.toString(field.getInt(obj));  
  29.             } else if (fieldTypeClass == long.class) {  
  30.                 value = Long.toString(field.getLong(obj));  
  31.             } else if (fieldTypeClass == short.class) {  
  32.                 value = Short.toString(field.getShort(obj));  
  33.             } else if (fieldTypeClass == float.class) {  
  34.                 value = Float.toString(field.getFloat(obj));  
  35.             } else if (fieldTypeClass == double.class) {  
  36.                 value = Double.toString(field.getDouble(obj));  
  37.             } else if (fieldTypeClass == byte.class) {  
  38.                 value = Byte.toString(field.getByte(obj));  
  39.             } else if (fieldTypeClass == char.class) {  
  40.                 value = Character.toString(field.getChar(obj));  
  41.             } else if (fieldTypeClass == boolean.class) {  
  42.                 value = Boolean.toString(field.getBoolean(obj));  
  43.             }  
  44.         } catch (Exception ex) {  
  45.             ex.printStackTrace();  
  46.             value = null;  
  47.         }  
  48.         return value;  
  49.     }  
  50.   
  51.     /** 
  52.      * 输出对象的字段,当对象的字段为Collection或者Map类型时, 
  53.      * 要调用exportObject方法继续处理 
  54.      * @param obj 被处理的对象 
  55.      * @throws Exception 
  56.      */  
  57.     public void exportFields(Object obj) throws Exception {  
  58.         Exportable exportable = obj.getClass().getAnnotation(Exportable.class);  
  59.         if (exportable != null) {  
  60.             if (exportable.value().length() > 0) {  
  61.                 //System.out.println("Class annotation Name:"+exportable.value());  
  62.             } else {  
  63.                 //System.out.println("Class annotation Name:"+exportable.name());  
  64.             }  
  65.         } else {  
  66.             //System.out.println(obj.getClass()+"类不是使用Exportable标注过的");  
  67.         }  
  68.   
  69.         //取出对象的成员变量  
  70.         Field[] fields = obj.getClass().getDeclaredFields();  
  71.   
  72.         for (Field field : fields) {  
  73.             //获得成员变量的标注  
  74.             Persistent fieldAnnotation = field.getAnnotation(Persistent.class);  
  75.             if (fieldAnnotation == null) {  
  76.                 continue;  
  77.             }  
  78.             //重要:避免java虚拟机检查对私有成员的访问权限  
  79.             field.setAccessible(true);  
  80.             Class typeClass = field.getType();  
  81.             String name = field.getName();  
  82.             String value = getFieldValue(field, typeClass, obj);  
  83.   
  84.             //如果获得成员变量的值,则输出  
  85.             if (value != null) {  
  86.                 System.out.println(getIndent() + "<" + name + ">\n"  
  87.                         + getIndent() + "\t" + value + "\n" + getIndent() + "</" + name + ">");  
  88.             } //处理成员变量中类型为Collection或Map  
  89.             else if ((field.get(obj) instanceof Collection)  
  90.                     || (field.get(obj) instanceof Map)) {  
  91.                 exportObject(field.get(obj));  
  92.             } else {  
  93.                 exportObject(field.get(obj));  
  94.             }  
  95.         }  
  96.     }  
  97.   
  98.     //缩进深度  
  99.     int levelDepth = 0;  
  100.     //防止循环引用的检查者,循环引用现象如:a包含b,而b又包含a  
  101.     Collection<Object> cyclicChecker = new ArrayList<>();  
  102.   
  103.     /** 
  104.      * 返回缩进字符串 
  105.      * @return 
  106.      */  
  107.     private String getIndent() {  
  108.         String s = "";  
  109.         for (int i = 0; i < levelDepth; i++) {  
  110.             s += "\t";  
  111.         }  
  112.         return s;  
  113.     }  
  114.   
  115.     /** 
  116.      * 输出对象,如果对象类型为Collection和Map类型, 
  117.      * 则需要递归调用exportObject进行处理 
  118.      * @param obj 
  119.      * @throws Exception 
  120.      */  
  121.     public void exportObject(Object obj) throws Exception {  
  122.         Exportable exportable = null;  
  123.         String elementName = null;  
  124.   
  125.         //循环引用现象处理  
  126.         if (cyclicChecker.contains(obj)) {  
  127.             return;  
  128.         }  
  129.   
  130.         cyclicChecker.add(obj);  
  131.   
  132.         //首先处理Collection和Map类型  
  133.         if (obj instanceof Collection) {  
  134.             for (Iterator i = ((Collection) obj).iterator(); i.hasNext();) {  
  135.                 exportObject(i.next());  
  136.             }  
  137.         } else if (obj instanceof Map) {  
  138.             for (Iterator i = ((Map) obj).keySet().iterator(); i.hasNext();) {  
  139.                 exportObject(i.next());  
  140.             }  
  141.         } else {  
  142.             exportable = obj.getClass().getAnnotation(Exportable.class);  
  143.             //如果obj已经被Exportable Annotation修饰过了(注意annotation是具有继承性的),  
  144.             //则使用其name作为输出xml的元素name  
  145.             if (exportable != null) {  
  146.                 if (exportable.value().length() > 0) {  
  147.                     elementName = exportable.value();  
  148.                 } else {  
  149.                     elementName = exportable.name();  
  150.                 }  
  151.             }  
  152.             //未被修饰或者Exportable Annotation的值为空字符串,  
  153.             //则使用类名作为输出xml的元素name  
  154.             if (exportable == null || elementName.length() == 0) {  
  155.                 elementName = obj.getClass().getSimpleName();  
  156.             }  
  157.             //输出xml元素头  
  158.             System.out.println(getIndent() + "<" + elementName + ">");  
  159.             levelDepth++;  
  160.             //如果没有被修饰,则直接输出其toString()作为元素值  
  161.             if (exportable == null) {  
  162.                 System.out.println(getIndent() + obj.toString());  
  163.             }  
  164.             else {  //否则将对象的成员变量导出为xml  
  165.                 exportFields(obj);  
  166.             }  
  167.             levelDepth--;  
  168.             //输出xml元素结尾  
  169.             System.out.println(getIndent() + "</" + elementName + ">");  
  170.   
  171.         }  
  172.         cyclicChecker.remove(obj);  
  173.     }  
  174.   
  175.     public static void main(String[] argv) {  
  176.         try {  
  177.             AddressForTest ad = new AddressForTest("China""Beijing",  
  178.                     "Beijing""winnerStreet""10");  
  179.             ExportToXml test = new ExportToXml();  
  180.             ArrayList<String> telephoneList = new ArrayList<>();  
  181.             telephoneList.add("66608888");  
  182.             telephoneList.add("66608889");  
  183.             ArrayList<AddressForTest> adList = new ArrayList<>();  
  184.             adList.add(ad);  
  185.             ContactBookForTest adl = new ContactBookForTest("coolBoy",  
  186.                     18, telephoneList, adList, "some words");  
  187.             test.exportObject(adl);  
  188.         } catch (Exception ex) {  
  189.             ex.printStackTrace();  
  190.         }  
  191.     }  
  192. }  
  在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绑定,运行结果如下:
[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <contactbook>  
  2.     <friendName>  
  3.         coolBoy  
  4.     </friendName>  
  5.     <age>  
  6.         18  
  7.     </age>  
  8.     <String>  
  9.         66608888  
  10.     </String>  
  11.     <String>  
  12.         66608889  
  13.     </String>  
  14.     <address>  
  15.         <country>  
  16.             China  
  17.         </country>  
  18.         <province>  
  19.             Beijing  
  20.         </province>  
  21.         <city>  
  22.             Beijing  
  23.         </city>  
  24.         <street>  
  25.             winnerStreet  
  26.         </street>  
  27.         <doorplate>  
  28.             10  
  29.         </doorplate>  
  30.     </address>  
  31.     <note>  
  32.         some words  
  33.     </note>  
  34. </contactbook>  

五、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的功能。实际上,APT在Java 8中已经被移除,可参看http://openjdk.java.net/projects/jdk8/features,其中的JEP 117。它的功能已经完全集成到javac编译器中去了。
  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 processor以处理源文件中所涉及的annotation。接下来,一个合适的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类全名,每个类为一个单独行。

  在JDK 8中,tools.jar中的这4个包已经被移除了,因此我们不再举apt的实际例子来分析了。到此为止 :)


参考文献:

http://www.blogjava.net/ericwang/archive/2005/12/13/23743.html

http://www.blogjava.net/weidagang2046/articles/28321.html

http://www.blogjava.net/weidagang2046/articles/27958.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值