首页 > 代码库 > Java注释Annotation

Java注释Annotation



Java注释Annotation

JDK 5开始,Java增加了对元数据(MetaData)的支持,也就是Annotation(注释)。Annotation提供了一种为程序元素设置元数据的方法,程序元素包括修饰包、类、构造器、方法、成员变量、参数、局部变量,从某些方面来看,Annotation就想修饰符一样,可用于程序元素的声明,这些信息被存储在Annotation”name = value”对中。

需要注意的是, Annotation是一个接口,程序可以通过反射来获取指定程序元素的Annotation对象,然后通过Annotation对象来取得注释里的元数据,且不会影响程序代码的执行,无论增加、删除Annotation,代码都始终如一地执行。如果希望让程序中的Annotation在运行时起一定的作用,只有通过某种配套的工具对Annotation中的信息进行访问和处理,访问和处理Annotation的工具统称为APT(Annotation ProcessingTool)

 

一、Annotation基础

前面已经讲到APT负责提取Annotation里包含的元数据,那么我们先了解一下4个基本Annotation的使用,需要在Annotation前面加上@符号,以修饰符的形式放在程序元素的前面。

4个基本的Annotation如下:

  • @Override

  • @Deprecated

  • @SuppressWarnings

  • @SafeVarargs

1@Override——限定重写父类方法

顾名思义,@Override就是用来重写方法的,它只能作用于方法,不能作用于其他程序元素,它可以强制一个子类必须覆盖父类的方法。通常情况下会被省略,例如下面的程序指定了子类Circledraw()方法必须重写父类方法:

class Shape{

  void draw(){

     System.out.println("画一个形状");

  }

}

class Circleextends Shape{

  //@Override省略效果相同

  @Override

  void draw() {

     System.out.println("画一个圆形");

  }

}

但是@Override作为一个标识,可以告诉我们该方法在其父类中是不是存在,或者我们需要重写的方法是否跟父类中的方法一致,例如上面的程序,如果我把draw()写成darw(),系统将会报编译错误。

 

2@Deprecated——标示已过时

用于表示某个程序元素(类、方法等)已过时,当其他程序使用已过时的类、方法时,编译器将会给出警告。例如:

publicclassTest{

  publicstaticvoid main(String[] args) {

     //下面调用shapedraw方法时,编译器会给出警告

     new Shape().draw();

  }

}

 

class Shape{

  @Deprecated

  voiddraw(){

     System.out.println("画一个形状");

  }

}

 

3@supressWarnings——抑制编译器警告

指示被该Annotation修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告。@SupressWarnings会抑制作用于该程序元素的所有子元素,例如,使用@SupressWarnings修饰某个类取消显示某个编译器警告,同时又修饰该类里的某个方法取消显示另一个编译器警告,那么该方法将会同时取消显示这两个编译器警告。

在通常情况下,如果程序中使用没有泛型限制的集合,将会引起编译器的警告,为了避免编译器的警告,可以使用@SupressWarnings修饰,例如:

//关闭整个类里的编译器警告

@SuppressWarnings("unchecked")

publicclass Test{

  publicstaticvoid main(String[]args) {

     List<String> tempList = newArrayList();

  }

}

 

4Java7堆污染警告与@SafeVarargs

要理解堆污染,首先看一个例子:

    List list =new ArrayList<Integer>();

     list.add(10);//添加元素时引发unchecked异常

     //下面代码引起未经检查的转换的警告,但编译、运行时完全正常

     List<String> temp = list;

     //但只要访问temp里的元素,就会引起运行时异常

     System.out.println(temp.get(0));

如上所示,将list赋值给temp就造成了所谓的堆污染当把一个不带泛型的对象赋值给一个带泛型的变量时,往往就会引发堆污染

对于形参个数可变的方法,如果该形参的类型是泛型,就更容易引发堆污染了,例如下面的方法中,相当于把List<String>赋值给了List。该方法首先将listArraylistStrArray指向同一内存地址,然后将整型集合赋值给listArray的第一个元素,再使用listStrArray取出第一个元素时,将引发ClassCastException异常:

 publicvoidtemp(List<String>... listStrArray){

     List[] listArray = listStrArray;

     List<Integer> tempList = Arrays.asList(99);

     listArray[0] = tempList;

     String s = listStrArray[0].get(0);

}

而我们这里要讲的@SafeVarargs,就是Java7专门用来一直堆污染警告而提供的Annotation。除此之外,另一种方法就是我们上面所将的,使用@SuppressWarning(“unchecked”)修饰。

 

二、JDK的元Annotation

你可能会觉得这4Annotation使用起来不太方便,认为Annotation只有这么几个功能。其实,这只是Annotation的凤毛麟角,如果我们想深入了解Annotation,那就得自定义Annotation

在讲自定义Annotation之前,首先要说的是元Annotation。因为Annotation并不是程序元素,甚至对其进行增删改也不会影响程序的正常运行。那么如果我们想让Annotation在程序运行时也起作用,该怎么办呢?我们要讲的元Annotation就是来干这个的,它用来修饰其他的Annotation定义,使Annotation具有不同的作用域。

1、使用@Retention

我们在创建自定义Annotation时,可以使用@Retention来修饰这个Annotation,用于指定被修饰的Annotation的作用域。@Retention包含一个RetentionPolicy类型的value成员变量,其值可以使如下3个:

  • RetentionPolicy.Class:默认值,编译器将吧Annotation存储于class文件中,但运行Java程序时,JVM不会保留Annotation,即只存储但不参与程序运行;

  • RetentionPolicy.RUNTIME:编译器将吧Annotation存储于class文件中,运行Java程序时,JVM也会保留Annotation,即存储且参与程序运行;

  • RetentionPolicy.SOURCE:编译器不会存储Annotationclass文件中,运行Java程序时JVM也不会保留Annotation,即不存储不保留。

很多时候我们需要通过反射获取注释信息,所以就需要使用value属性值为RetentionPolicy.RUNTIME@Retention,例如下面我们定义了一个名为ParamAnnotation,并且可以通过反射来获取它的注释信息:

@Retention(RetentionPolicy.RUNTIME)

public@interfaceParam {

  long id();

  String name();

  int sex()default 1;

}

 

2、使用@Target

@Target也可以用来修饰Annotation定义,它用于指定被修饰的Annotation能用于修饰哪些程序单元,其value值有如下几个:

  • ElementType.ANNOTATION_TYPE:指定该策略的Annotation只能修饰Annotation

  • ElementType.CONSTRUCTOR:只能修饰构造器;

  • ElementType.FIELD:只能修饰成员变量;

  • ElementType.LOCAL_VARIABLE:只能修饰局部变量;

  • ElementType.METHOD:只能修饰方法定义;

  • ElementType.PACKAGE:只能修饰包定义;

  • ElementType.PARAMETER:可以修饰参数;

  • ElementType.TYPE:可以修饰类、接口(包括注释类型)或枚举定义。

下面的例子中,定义Param只能修饰方法:

@Target(ElementType.METHOD)

public@interfaceParam {

  long id();

  String name();

  int sex()default 1;

}

3、使用@Document

@Document用于指定被它修饰的Annotation类将被javadoc工具提取成文档。如果定义Annotation类时使用了@Document修饰,则所有使用该Annotation修饰的程序元素的API文档中将会包含该Annotation说明。举个例子来说明:

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

@Documented

public@interfaceParam {

  long id();

  String name();

  int sex()default 1;

}

publicclass Dulven {

  publicstaticvoid main(String[]args) {}

  @Param(id=1001001,name="GISirFive")

  publicvoid toPerson() {}

}

javadoc提取API,打开API后是这样的:

如果在定义Param时不加@Document,就不会显示@Param()

4、使用@Inherited

@Inherited用于指定被其修饰的Annotation定义具有继承性,被该Annotation修饰的类如果有子类,则子类将会自动被该Annotation修饰。举个例子:

@Inherited

public@interfaceParam {

  long id();

  String name();

  int sex()default 1;

}

@Param(id=110, name="singer")

class Base{ }

 

publicclass Dulvenextends Base{

  publicstaticvoid main(String[]args) {

  System.out.println(Dulven.class.isAnnotationPresent(Param.class));

  }

}

上面程序运行会输出true                                              

 

三、自定义Annotation

除了这4个基本的Annotation,细心的程序员可能会发现还有很多未知的Annotation。其实除了这4个基本的Annotation外,大多数都是我们自定义的,我们可以通过自定义Annotation来使代码更通俗易懂。

1、定义Annotation

定义一个新的Annotation与定义一个接口类似,需要使用@interface关键字,例如下面定义了一个名为ParamAnnotation,并在Test类中使用它:

public@interfaceParam {   }

 

@Param

publicclass Test {

  publicstaticvoid main(String[]args) {    }

}

在默认情况下,Annotation可用于修饰任何程序元素,包括类、接口、方法等。

如普通方法一样,Annotation还可以带成员变量,Annotation的成员变量在Annotation定义中以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型,例如:

public@interfaceParam {

  long id();

  String name();

  int sex()default 1;

}

一旦在Annotation里定义了成员变量,使用时就需要为其指定值,当然我们也可以为成员变量指定默认值,默认值制定方法如同上面的sex,其默认值为1。这样我们在使用时就不需要为其指定值了,例如:

@Param(id=1001, name="旺旺")

publicclass Animal {

  publicstaticvoid main(String[]args) {}

}

我们可以将Annotation按是否包含成员变量分为两类:

  • 标记Annotation:指没有定义成员变量的Annotation。这种Annotation仅利用自身的存在与否来为我们提供信息,例如@Override@Deprecated等。

  • 元数据Annotation:指包含成员变量的Annotation,因为它们可以接受更多的元数据。

 

2、提取Annotation信息

当开发者使用Annotation修饰了类、方法、Field等成员之后,正如Annotation的定义所言,Annotaion不能影响程序代码的执行,无论增加、删除Annotation,代码都始终如一的执行。只有通过apt工具对Annotation中的信息进行访问和处理,我们才能让程序中的Annotation在程序运行时起一定的作用。所以这些Annotation不会自己生效,必须由开发者提供相应的工具来提取并处理Annotation信息。

Java使用Annotation接口来代表程序元素前面的注释,该接口是所有Annotation类型的付接口。Java5java.lang.reflect(主要包含一些实现反射功能的工具类)包下新增了AnnotatedElement接口,该接口代表程序中可以接受注释的程序元素。该接口主要有如下几个实现类:

Class:类定义

Constructor:构造器定义

Field:类的成员变量定义

Method:类的方法定义

Package:类的包定义

Java5开始,该包所提供的反射API扩充了读取运行时Annotation的能力。当一个Annotation类型被定义为运行时Annotation(如何定义运行时,一会第三章会讲解)后,该Annotation才会在运行时可见,JVM才会在状态*.class文件时读取保存在class文件中的Annotation

AnnotatedElement接口是所有程序元素(如ClassMethodConstructor等)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象(如ClassMethodConstructor等)之后,程序就可以调用该对象的如下3个方法来访问Annotation信息。

  • getAnnotation(Class<T>annotationClass):返回该元素上存在的指定类型的注释,如果该类型的注释不存在,则返回null

  • Annotation[]getAnnotations():返回该程序元素上存在的所有注释;

  • booleanisAnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否存在指定类型的注释,若存在则返回true,否则返回false

下面的程序用于获取Dulven类中的toPerson方法里的所有注释,并将其打印出来:

publicclass Dulven {

  publicstaticvoid main(String[]args)throws NoSuchMethodException, SecurityException,ClassNotFoundException {

     Annotation[] annotations = Class.forName("com.Dulven").getMethod("toPerson").getAnnotations();

     for (Annotation annotation : annotations)

         System.out.println(annotation);

  }

  @Param(id=1001001,name="小明")

  publicvoid toPerson() {}

}

如果需要获取某个注释里的元数据,则可以将注释强制转换成所需的注释类型,然后通过注释对象的抽象方法来访问这些元数据,例如:

publicclass Dulven {

  publicstaticvoid main(String[]args)throws NoSuchMethodException, SecurityException,ClassNotFoundException {

     Dulven dulven = new Dulven();

     Annotation[] annotations =dulven.getClass().getMethod("toPerson").getAnnotations();

     for (Annotation annotation : annotations) {

         if(annotationinstanceofParam){

            System.out.println("注释内容:" + annotation);

            Param param = (Param)annotation;

            System.out.println("id:" + param.id() + ", name:" + param.name() +", sex:" + param.sex());

         }

     }

  }

  @Param(id=1001001,name="小明")

  publicvoid toPerson() {}

}

3、使用Annotation的示例

下面通过两个例子,来帮助大家深入理解Annotation

下面创建了一个名为KangshifuAnnotation,它没有任何成员变量,只是用来标记哪些方法是属于康师傅的:

//使该Annotation在程序运行时起作用

@Retention(RetentionPolicy.RUNTIME)

//Annotation只能修饰方法

@Target(ElementType.METHOD)

public@interfaceKangshifu {  }

下面创建了一个BJFactory类,里面有多个方法,@Kangshifu用来标记北京工厂中哪些方法是可以生产康师傅产品的:

publicclass BJFactory {

  //用来标记该方法用于生产康师傅产品

  @Kangshifu

  publicstaticvoid creatType1(){  }

  publicstaticvoid creatType2(){  }

  @Kangshifu

  publicstaticvoid creatType3(){  }

  @Kangshifu

  publicstaticvoid creatType4(){

     thrownewNullPointerException("原料不足");

  }

  publicstaticvoid creatType5(){  }

}

我们已经知道,仅仅使用注释来标记程序元素,对程序是不会有任何影响的,即使使用了@Retention修饰的Annotation也不行,这是Java注释的一条正要原则。要让程序中的注释起作用,接下来必须为这些注释提供一个处理工具。

下面的注释处理工具会分析目标类,如果目标类中的方法使用的@Kangshifu注释修饰,则通过反射来运行该测试方法:

//康师傅产品线

publicclass KSFProcess {

  publicstaticvoidprocessFilter(String clazz)throwsSecurityException,

     ClassNotFoundException{

     int passed = 0;

     int failed = 0;

     //遍历clazz对应的类里的所有方法

     for (Method m : Class.forName(clazz).getMethods()){

         //该方法使用了@Kangshifu修饰

         if(m.isAnnotationPresent(Kangshifu.class)){

            try {

               //调用m方法

               m.invoke(null);

               passed++;

            } catch (Exception e) {

               System.out.println("生产流程" + m + "运行异常:" + e.getCause());

               failed++;

            }

         }

     }

     //统计测试结果

     System.out.println("该工厂共检测到生产流程" + (passed + failed) + "个,其中"

            + failed + "个失败," + passed + "个正常!");

  }

}

KSFProcess类目前只包含一个processFilter方法,是康师傅公司用来检测各代理生产商的产品线的,该方法将会分析clazz参数所代表的类,并运行该类里使用的@Kangshifu修饰的方法。

 publicstaticvoid main(String[] args) {

     try {

         KSFProcess.processFilter("com.BJFactory");

     } catch (SecurityException | ClassNotFoundExceptione) {

         //TODO自动生成的 catch

         e.printStackTrace();

     }

}

执行processFilter方法,将打印如下信息:

生产流程publicstatic void com.BJFactory.creatType4()运行异常:java.lang.NullPointerException:原料不足

该工厂共检测到生产流程3个,其中1个失败,2个正常!

上面这个例子只是一个标记Annotation,程序通过判断该Annotation是否存在,来决定是否执行方法。下面的例子中,介绍了如何使用Annotation来简化事件编程。我们知道一般情况下都是通过某个方法来为事件源绑定事件监听器,下面的例子中,是通过@ActionListener来为事件源绑定监听器:

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.FIELD)

public@interfaceActionListenerFor {

  //定义一个成员变量,用于设置元数据

  //listener成员变量用于保存监听器实现类

  Class<? extends ActionListener> listener();

}

首先定义了一个@ActionListenerFor注释,我们要使用该注释指定一个listener成员变量,该成员变量用于指定监听器的实现类。下面程序使用@ActionListenerFor注释来为两个按钮绑定事件监听器:

publicclass AnnotaionTest {

  private JFrameframe =new JFrame("使用注释绑定事件监听器");

  //使用Annotationok按钮绑定监听器

  @ActionListenerFor(listener = OKListener.class)

  private JButtonbtnOK =new JButton("确定");

  @ActionListenerFor(listener = CancelListener.class)

  private JButtonbtnCancel =new JButton("取消");

  

  publicvoid init(){

     JPanel panel = new JPanel();

     panel.add(btnOK);

     panel.add(btnCancel);

     frame.add(panel);

     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

     frame.pack();

     frame.setVisible(true);

  }

  publicstaticvoid main(String[]args) {

     new AnnotaionTest().init();

  }

  

}

 

class OKListenerimplements ActionListener{

 

  @Override

  publicvoid actionPerformed(ActionEvent e) {

     JOptionPane.showMessageDialog(null,"单击了确认按钮");;

  }

  

}

 

class CancelListenerimplements ActionListener{

 

  @Override

  publicvoid actionPerformed(ActionEvent e) {

     JOptionPane.showMessageDialog(null,"单击了取消按钮");

  }

  

}

上面程序定义了两个Jbutton按钮,并使用@ActionListenerFor注释为其绑定了事件监听器,其中listener用于指定每个按钮的监听器实现类。

看到这里,相信新手们都会疑惑不解,因为我们不知道监听器类和注释、注释和按钮是怎么联系起来的,即监听器是如何作用到按钮上的呢?正如之前所说,如果仅仅在程序中使用注释是不会起到任何作用的,我们需要通过注释处理工具来处理程序中的注释,使其与其修饰的元素联系起来。

所以,我们需要定义一个注释处理类,用来分析目标对象中的所有Field,如果该Field前使用了@ActionListenerFor修饰,则取出该Annotation中的listener元数据,并根据该数据来绑定事件监听器:

publicclass ActionListenerInstaller {

  //处理Annotation的方法,其中object是包含Annotation的对象

  publicstaticvoidprocessAnnotations(Object object){

     try {

         //获取Object对应的类

         Class clazz =object.getClass();

         //遍历该类下所有已声明的Field

         for (Field field : clazz.getDeclaredFields()) {

            //将该Feild设置成可自由访问

            field.setAccessible(true);

            //判断该Field是否被@ActionListenerFor修饰

            boolean b = field.isAnnotationPresent(ActionListenerFor.class);

            if(!b)continue;

            ActionListenerFor listenerFor = field.getAnnotation(ActionListenerFor.class);

            //获取Field实际对应的对象

            Object fObject = field.get(object);

            //JButton继承于AbstractButton,如果fObject存在且继承于AbstractButton

            if(fObject !=null && fObjectinstanceof AbstractButton){

               //获取注释里的元数据listener

               Class<? extends ActionListener> tempListener =listenerFor.listener();

               //使用反射来创建listener类的对象

               ActionListener actionListener =tempListener.newInstance();

               AbstractButton button =(AbstractButton)fObject;

               button.addActionListener(actionListener);

            }

         }

     } catch (Exception e) {

         e.printStackTrace();

     }

  }

}

最后,我们需要将处理类在ActionTest类的init()方法中执行:

 publicvoid init(){

     JPanel panel = new JPanel();

     panel.add(btnOK);

     panel.add(btnCancel);

     frame.add(panel);

     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

     frame.pack();

     frame.setVisible(true);

     ActionListenerInstaller.processAnnotations(this);

}

运行上面的示例程序,点击确定,会弹出消息对话框。

 

四、编译时处理Annotation

APTAnnotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测,找出其中的Annotation后,对Annotation进行额外的处理。

使用APT的主要目的,是简化开发者的工作量,因为APT可以在编译程序源码的同时生成一些附属文件(比如源文件、类文件、程序发布描述文件等),这些负数文件的内容也都与源代码相关,所以,使用APT可以代替传统的对代码信息和附属文件的维护工作。

了解过Hebernate早期版本的读者都知道:没写一个Java类文件,还必须额外地维护一个Hibernate映射文件(名为*.hbm.xml的文件,也有一些工具可以自动生成)。下面将使用Annotation来简化这不操作。

Java提供的javac.exe工具有一个-processor选项,该选项可指定一个Annotation处理器,如果在编译Java源文件时通过该选项指定了Annotation处理器,那么这个Annotation处理器将会在编译时提取并处理Java源文件中的Annotation

每个Annotation处理器都需要实现javax.annotation.processing包下的Processor接口。不过实现该接口必须实现它里面所有的方法,因此通常会采用继承AbstractProcessor的方式来实现Annotation处理器。一个Annotation处理器可以处理一种或者多种Annotation类型。

下面示范一下如何使用APT根据源文件中的注释来生成额外的文件。首先定义3Annotation类型,分别作用于持久化类、标识属性和普通成员属性。

@Retention(RetentionPolicy.SOURCE)

@Target(ElementType.TYPE)

@Documented

public@interfacePersistent {

  String table();

}

上面这个Annotation用于修饰类、接口等类型声明,这个Annotation使用了@Retention指定其仅在源码中存在,无法通过反射来读取其信息。

@Retention(RetentionPolicy.SOURCE)

@Target(ElementType.FIELD)

@Documented

public@interfaceId {

  String column();

  String type();

  String generator();

}

上面这个Annotation用于修饰标识属性。

@Retention(RetentionPolicy.SOURCE)

@Target(ElementType.FIELD)

@Documented

public@interfaceProperty{

  String column();

  String type();

}

上面这个Annotation用于修饰普通成员变量。下面我们写一个简单的Java类,并使用这三个Annotation来修饰。

@Persistent(table="person_inf")

publicclass Person {

  @Id(column="person_id", type="integer", generator="identity")

  privateintid;

  @Property(column="person_name", type="string")

  private Stringname;

  @Property(column="person_age", type="integer")

  privateintage;

  //无参数构造器

  public Person() { }

  

  public Person(int id, String name, int age){

     this.id = id;

     this.name = name;

     this.age = age;

  }

 

  publicint getId() {

     returnid;

  }

 

  publicvoid setId(int id) {

     this.id = id;

  }

 

  public String getName(){

     returnname;

  }

 

  publicvoid setName(String name) {

     this.name = name;

  }

 

  publicint getAge() {

     returnage;

  }

 

  publicvoid setAge(int age) {

     this.age = age;

  }

}

下面我们为这三个Annotation提供一个Annotation处理器,该处理器的功能是根据注释来生成一个Hibernate映射文件(如果不懂Hibernate也没有关系,只需要知道我们可以根据这些Annotation来生成一份xml即可)。

@SupportedSourceVersion(SourceVersion.RELEASE_7)

//指定可处理@Persistent@Id@Property三个Annotation

@SupportedAnnotationTypes({"Persistent","Id","Property"})

publicclassHibernateAnnotationProcessorextends AbstractProcessor{

  //循环处理每个需要处理的程序对象

  @Override

  publicboolean process(Set<?extends TypeElement>annotations,

         RoundEnvironmentroundEnv) {

     //定义一个文件输出流,用于生成额外的文件

     PrintStreamps = null;

     try {

         for (Element t :roundEnv.getElementsAnnotatedWith(Persistent.class)){

            //获取正在处理的类名

            NameclazzName = t.getSimpleName();

            //获取类定以前的@Persistent Annotation

            Persistent per = t.getAnnotation(Persistent.class);

            ps= newPrintStream(newFileOutputStream(clazzName + ".hbm.xml"));

            ps.println("<?xml version=\"1.0\"?>");

            ps.println("<!DOCTYPE hibernate-mapping PUBLIC");

            ps.println("\"-//Hibernate/HibernateMapping DTD 3.0//EN\"");

            ps.println("  \"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd\">");

            ps.println("<hibernate-mapping>");

            ps.print("<class name=\"" + t);

            //输出pertable()的值

            ps.println("\" table=\""+ per.table() +"\">");

            for (Element f :t.getEnclosedElements()) {

               //只处理Field上的Annotation

               if(f.getKind() ==ElementKind.FIELD){

                   //获取Field定义前的@Id Annotation

                  Id id = f.getAnnotation(Id.class);

                  //@Id Annotation存在时,输出<id.../>元素

                  if(id !=null){

                     ps.println("   <idname=\"" + f.getSimpleName()

                            +"\" column=\"" + id.column()

                            +"\" type=\"" + id.type() +"\">");

                     ps.println("   <generatorclass=\"" + id.generator() +"\"/>");

                     ps.println("   </id>");

                  }

                  //获取Field定义前的@Property Annotation

                  Property p = f.getAnnotation(Property.class);

                  if(p !=null){

                     ps.println("   <propertyname=\"" + f.getSimpleName()

                            +"\" column=\"" + p.column()

                            +"\" type=\"" + p.type() +"\" />");

                  }

               }

            }

            ps.println("</class>");

            ps.println("</hibernate-mapping>");

            

         }

     }catch(Exception e) {

         e.printStackTrace();

     }

     finally{

         if(ps !=null){

            try {

               ps.close();

            }catch(Exception e2) {

               e2.printStackTrace();

            }

         }

     }

     returntrue;

  }

}

上面的Annotation处理器其实非常简单,与前面通过反射来获取Annotation信息不通的是,这个Annotation处理器使用RoundEnviroment来获取Annotation信息,RoundEnviroment里包含了一个getElementsAnnotatedWith()方法,可以根据Annotation获取需要处理的程序单元,这个程序单元由Element代表。Element里包含了一个getKind()方法,该方法返回Element所代表的程序单元,返回值可以使ElementKind.Class(类)、ElementKind.Field(成员变量)。。。。

除此之外,Element还包含一个getEnclosedElements()方法,该方法可用于获取该Element里定义的所有程序单元,包括Field、方法、构造器、内部类等。

接下来程序只处理Field前面的Annotation,因此程序先判断这个Element必须是ElementKind.FIELD

再接下来程序调用了Element提供的getAnnotation(Class clazz)方法来获取修改该ElementAnnotation。获取到Field上的@Id@Property之后,接下来就根据他们提供的信息执行输出。

提供了上面的Annotation处理器类之后,接下来就可以使用带-processor选项的javac.exe命令来编译Person.java了。例如如下命令:

Javac–processor HibernateAnnotationProcessor Person.java

使用上一句的前提是HibernateAnnotationProcessor已经编译,即当前目录下存在HibernateAnnotationProcessor.class文件。

通过上面的命令编译Person.java后,将可以看到在相同路径下生成了一个Person.hbm.xml文件,该文件就是根据Person.java里的Annotation生成的。该文件内容如下:

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC

    "-//Hibernate/HibernateMapping DTD 3.0//EN"

    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name=">Person"table="person_inf">

          <idname="id" column="person_id" type="integer">

          <generatorclass="identity"/>

          </id>

          <propertyname="name" column="person_name" type="string"/>

          <propertyname="age" column="person_age" type="integer"/>

    </class>

</hibernate-mapping>

对比上面的XML文件和Person.java中的Annotation就可以看出,它们是完全对应的,因为XML文件是根据Person.java中的Annotation生成的。从生成的这份XML文件可以看出,通过使用APT工具确实可以简化程序开发,程序员只需要把一写关键信息通过Annotation写在程序中,然后使用APT工具就可以生成额外的文件。

 

总结文章,主要介绍了JavaAnnotation支持,包括3个基本Annotation的用法,4个用于修饰Annotation的元Annotation的用法,如何自定义并使用Annotation,以及如何使用APT工具来处理Annotation。通过使用Annotation可以为程序提供一写元数据,这些元数据可以在编译、运行时被读取,从而提供更多额外的处理信息。

 

 

 

Java注释Annotation