疯狂Java学习笔记(53)------------Annotation(注释)第二篇

JDK的元Annotation

JDK除了java.lang下提供了3个基本Annotation之外,还在java.lang.annotation包下提供了四个Meta Annotation(元Annotation),这四个都是用于修饰其它Annotation定义

(1)使用@Retention

  @Retention只能用于修饰一个Annotation定义,用于该Annotation可以保留多长时间,@Retention包含一个RetentionPolicy类型的value成员变量,所以使用@Retention时必须为该value成员变量指定值.

value成员变量的值只能是如下三个

RetentionPolicy.CLASS:编译器将把注释记录在class文件中。当运行Java程序时,JVM不再保留注释。这是默认值

RentionPolicy.RUNTIME:编译器将把注释记录在class文件中。当运行java程序时,JVM也会保留注释,程序也可以通过反射获取该注释。

RentionPolicy.SOURCE:编译器直接丢弃这种策略的注释。

  在前面的程序中,因为我们要通过反射获取注释信息,所以我们指定value属性值为RetentionPolicy.RUNTIME. 使用@Retention元数据Annotation可采用如下代码为value指定值.

//定义下面的Testable Annotation的保留到过行时

@Retention(value=RetentionPolicy.RUNTIME)

public @interface Testable{}

也可以采用如下代码来为value指定值

@Retention(RetentionPolicy.SOURCE)

public @interface Testable{}

  

上面代码使用@Retention元数据Annotation时,并未直接通过value=RetentionPolicy.SOURCE的方式来为成员变量指定值,这是因为如果Annotation的成员变量名为value时,程序中可以直接在Annotation后的括号里指定该成员变量的值,无须用name=value的形式.

说明

  如果我们定义的Annotation类型里只有一个value成员变量,使用该Annotation时可以直接在Annotation后的括号里指定value成员变量的值,无须使用name=value的形式。

(2)使用@Target

  @Target也是用于修饰一个Annotation定义,它用于指定被修饰的Annotation能用于修饰哪些程序元素。@Target Annotation也包含一个名为value的成员变量。该成员变量的值只能是如下几个

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

ElementType.CONSTRUCTOR:指定该策略的Annotation能修饰构造器

ElementType.FIELD:                 指定该策略的Annotation只能修饰成员变量

ElementType.LOCAL_VARIABLE:指定该策略的Annotation只能修饰局部变量

ElementType.METHOD             指定该策略的Annotation只能修饰方法定义

ElementType.PACKAGE           指定该策略的Annotaion只能修饰包定义

ElementType.PARAMETER      指定该策略的Annotation可以修饰参数

ElementType.TYPE                   指定该策略的Annotaion可以修饰类,接口(包括注释类型)或枚举定义

  与使用@Retention类似的是,使用@Target也可以直接在括号里指定value值,可以无须使用name=value的形式。如

下代码指定@ActionListenerFor Annotation只能修饰成员变量

@Target(ElementType.FIELD)

public @interface ActionListenerFor{}

如下代码指定@Testable Annotation只能修饰方法

@Target(ElementType.METHOD)

public @interface Testable{}

(3)使用@Documented

  @Documented用于指定该元Annotation修饰的Annotation类将被javadoc工具提取成文档,如果定义Annotatin类时使用了@Documented修饰,则所有使用该Annotation修饰的程序元素API文档中将会包含该Annotation说明

  

下面程序定义了一个Testable Annotation程序使用@Documented来修饰@Testable Annotation定义,所以该Annotation将被 javadoc工具提取

<span style="font-size:18px;color:#333333;">@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
//定义Testable Annotation将被javadoc工具提取
@Documented
public @interface Testable
{
}</span>

上面程序中的@Documented代码决定了所有使用@Testable Annotation的地方都会被javadoc工具提取到api文档中

  下面程序中定义了一个 MyTest类,该类中的infor方法使用Testable Annotation修饰

程序清单

<span style="font-size:18px;">public class MyTest
{
 //使用@Testable修饰info方法
 @Testable
 public void info()
 {
  System.out.println("info方法...");
 }
}</span>

(4)使用@Inherited

  @Inherited元Annotation指定被它修饰的Annotation将具有继承性。如果某个类使用了Annotaion(使用Annotation时使用了@Inherited修饰)修饰,则其子类将自动具有A注释。

下面使用@Inherited元数据注释定义了一个Inherited Annotation,该Annotation将具有继承性

程序清单

<span style="font-size:18px;">@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
// @Inherited
public @interface Inheritable
{
}</span>

  上面程序中表明了@Inheritable Annotation具有继承性,如果某个类使用了该Annotation 修饰,则该类的子类将自动具有@Inheritable Annotation

  下面程序定义了一个Base基类,该基类使用了@Inherited修饰,则Base类的子类将自动具有@Inherited Annotation

程序清单

<span style="font-size:18px;">//使用@Inheritable修饰的Base类
@Inheritable
class Base
{
}
//TestInheritable类只是继承了Base类,
//并未直接使用@Inheritable Annotiation修饰
public class TestInheritable extends Base
{
 <wbr>public static void main(String[] args)
 <wbr>{
 <wbr> <wbr>//打印TestInheritable类是否具有Inheritable Annotation
 <wbr> <wbr>System.out.println(TestInheritable.class.isAnnotationPresent(Inheritable.class));
 <wbr>}
}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span>

总结

   1.上面程序中的Base类使用了@Inheritable Annotation修饰,而该Annotaion具有可继承性,所以其子类也将具有@Inheritable Annotation,运行上面程序看到输出 true

2.如果将上面的Inheritable.java程序中的@Inherited注释或者删除,将会导致Inheritable Annotation不具有继承性,运行上面程序将会输出 false

使用APT处理Annotation

APT(Annotation processing tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。

  Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件.

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

  如果有过Hibernate开发经验的朋友可能知道每写一个Java文件,还必须额外地维护一个Hibernate映射文件(一个名为*.hbm.xml的文件,当然可以有一些工具可以自动生成),下面将使用Annotation来简化这步操作。

  为了使用系统的apt工具来读取源文件中的Annotation,程序员必须自定义一个Annotation处理器,编写Annotation处理器需要使用JDK lib目录中的tools.jar 里的如下4个包.

com.sun.mirror.apt:和APT交互的接口

com.sun.mirror.declaration:包含各种封装类成员,类方法,类声明的接口。

com.sun.mirror.type:包含各种封装源代码中程序元素的接口。

com.sun.mirror.util:提供了用于处理类型和声明的一些工具。

  每个Annotation处理器需要实现com.sun.mirror.apt包下的AnnotationProcessor接口,这个接口中定义了一个"process"方法,该方法是由apt调用Annotation处理器时将被用到的。

一个Annotation处理器可以处理一种或多种Annotation类型。

1.通常情况下,Annotation处理器实例是由其相应的工厂返回,Annotation处理器工厂应该实现AnnotationProcessorFactory接口,APT将调用工厂类的getProcessorFor方法来获得Annotation处理器。

2.在调用过程中,APT将提供给工厂类一个AnnotationProcessorEnvironment对象.

3.AnnotationProcessorEnvironment对象是APT工具与注释环境通信的途径。

  使用APT工具来处理源文件时,APT首先检测在源代码文件中包含哪些Annotation,然后APT将查找所需的处理器工厂,并由工厂来返回相应的Annotation处理器。如果该处理器工厂支持这些Annotaion,处理器工厂返回的Annotaion处理器将会处理这些Annotation,如果生成的源文件中再次包含Annotaion,APT将会重复上面过程,直至没有新文件生成。

  为了说明使用APT来根据源文件中的注释来生成额外的文件,下面将定义三个Annotation类型,分别用于修饰持久化类,标识属性和普通属性。

程序清单

修饰表属性

@Persistent(table="persons_table")
public class Person
{
 @IdProperty(column="person_id",type="integer",generator="identity")
 private int id;
 @Property(column="person_name",type="string")
 private String name;
 @Property(column="person_age",type="integer")
 private int age;

 public Person()
 {
 }

 public Person(int id , String name , int age)
 {
  this.id = id;
  this.name = name;
  this.age = age;
 }

 public void setId(int id)
 {
  this.id = id;
 }
 public int getId()
 {
   return this.id;
 }
 public void setName(String name)
 {
  this.name = name;
 }
 public String getName()
 {
   return this.name;
 }

 public void setAge(int age)
 {
  this.age = age;
 }
 public int getAge()
 {
   return this.age;
 }

}

定义了三个Annotation之后,下面我们提供一个简单的Java类文件,这个Java类文件使用了上面三个Annotation来修饰

@Persistent(table="persons_table")
public class Person
{
 @IdProperty(column="person_id",type="integer",generator="identity")
 private int id;
 @Property(column="person_name",type="string")
 private String name;
 @Property(column="person_age",type="integer")
 private int age;

 public Person()
 {
 }

 public Person(int id , String name , int age)
 {
  this.id = id;
  this.name = name;
  this.age = age;
 }

 public void setId(int id)
 {
  this.id = id;
 }
 public int getId()
 {
   return this.id;
 }
 public void setName(String name)
 {
  this.name = name;
 }
 public String getName()
 {
   return this.name;
 }

 public void setAge(int age)
 {
  this.age = age;
 }
 public int getAge()
 {
   return this.age;
 }

}

  上面Person类是一个非常普通的Java类,但这个普通的Java类使用了@Persistent,@IdProperty,@IdPropery三个Annotation。下面我们为这三个Annotation提供了一个Annotation处理器,该处理器的功能是根据注释来生成一个Hibernate的映射文件.

程序清单

import com.sun.mirror.apt.*;
import com.sun.mirror.declaration.*;
import com.sun.mirror.type.*;
import com.sun.mirror.util.*;

import java.beans.*;
import java.io.*;
import java.util.*;

import java.lang.reflect.*;
public class HibernateAnnotationProcessor implements AnnotationProcessor
{
 //Annotation处理器环境,是该处理器与APT交互的重要途径
 private AnnotationProcessorEnvironment env;
 //构造HibernateAnnotationProcessor对象时,获得处理器环境
 public HibernateAnnotationProcessor(AnnotationProcessorEnvironment env)
 {
  this.env = env;
 }
 //循环处理每个对象
 public void process()
 {
  //遍历每个class文件
  for (TypeDeclaration t : env.getSpecifiedTypeDeclarations())
  {
   //定义一个文件输出流,用于生成额外的文件
   FileOutputStream fos = null;
   //获取正在处理的类名
   String clazzName = t.getSimpleName();
   //获取类定义前的Persistent Annotation
   Persistent per = t.getAnnotation(Persistent.class);
   //当per Annotation不为空时才继续处理
   if(per != null)
   {
    try
    {
     //创建文件输出流
     fos = new FileOutputStream(clazzName + ".hbm.xml");
     PrintStream ps = new PrintStream(fos);
     //执行输出
     ps.println("<?xml version="1.0"?>");
     ps.println("<!DOCTYPE hibernate-mapping");
     ps.println(" PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"");
     ps.println("    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\">");
     ps.println("<hibernate-mapping>");
     ps.print(" <class name="" + t);
     //输出per的table()的值
     ps.println("" table="" + per.table() + "">");
     for (FieldDeclaration f : t.getFields())
     {
      //获取指定FieldDeclaration前面的IdProperty Annotation
      IdProperty id = f.getAnnotation(IdProperty.class);
      //如果id Annotation不为空
      if (id != null)
      {
       //执行输出
       ps.println("  <id name=""
        + f.getSimpleName()
        + "" column="" + id.column()
        + "" type="" + id.type()
        + "">");
       ps.println("   <generator class=""
        + id.generator() + ""/>");
       ps.println("  </id>");
      }
      //获取指定FieldDeclaration前面的Property Annotation
      Property p = f.getAnnotation(Property.class);
      //如果p Annotation不为空
      if (p != null)
      {
       //执行输出
       ps.println("  <property name=""
        + f.getSimpleName()
        + "" column="" + p.column()
        + "" type="" + p.type()
        + ""/>");
      }
     }
     ps.println(" </class>");
     ps.println("</hibernate-mapping>");
    }
    catch (Exception e)
    {
     e.printStackTrace();
    }
    finally
    {
     //关闭输出流
     try
     {
      if (fos != null)
      {
       fos.close();
      }
     }
     catch (IOException ex)
     {
      ex.printStackTrace();
     }
    }
   }
  }
 }
}

  上面的Annotation处理器比较简单,与前面通过反射来获取Annotation信息不同的是,这个Annotation处理器使用AnnotationProcessorEnvironment来获取Annotation信息,AnnotationProcessorEnvironment包含了一个getSpecifiedTypeDeclarations方法,可获取所有需要处理的类声明,这个类声明可包括类,接口,和枚举等声明,由TypeDeclaration对象表地示,与Classc对象的功能大致相似,区别只是TypeDeclaration是静态,只要有类文件就可以获得该对象,而Class是动态的,必须由虚拟机装载了指定类文件后才会产生。

TypeDeclaration又包含了如下三个常用方法来获得对应的程序元素。

getFields:获取该类声明里的所有成员变量声明,返回值是集合元素FieldDeclaration的集合

getMethods:获取该类声明里的所有成员声明,返回值是集合元素MethodDeclaration的集合

getPackage:获取该类声明里的包声明,返回值是TypeDeclaration

  上面三个方法返回的TypeDeclaration,FieldDeclaration,MethodDeclaration都可调用getAnnotation方法来访问修饰它们的Annotation,上面程序中就是获取不同程序元素的Annotation的代码。

  提供了上面的Annotation处理器类之后,还应该为该Annotation处理器提供一个处理工厂,处理工厂负责决定该处理器支持哪些Annotation,并通过getProcessorFor方法来生成一个Annotation处理哭对象。

程序清单如下

import com.sun.mirror.apt.*;
import com.sun.mirror.declaration.*;
import com.sun.mirror.type.*;
import com.sun.mirror.util.*;

import java.beans.*;
import java.io.*;
import java.util.*;
public class HibernateAnnotationFactory implements AnnotationProcessorFactory
{
 //所有支持的注释类型
 public Collection<String> supportedAnnotationTypes()
 {
  return Arrays.asList("Property" , "IdProperty" , "Persistent");
 }
 //返回所有支持的选项
 public Collection<String> supportedOptions()
 {
  return Arrays.asList(new String[0]);
 }
 //返回Annotation处理器
 public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds,AnnotationProcessorEnvironment env)
 {
  return new HibernateAnnotationProcessor(env);
 }
}

  提供了上面的处理器工厂后,就可以使用API工具来处理上面的Person.java源文件,并根据该源文件来生成一个XML文件。 APT工具位于JDK的安装路径的bin路径下。。

java Annotation的讲解就这些了,需要好好看一看呀!

博客借鉴http://blog.sina.com.cn/s/blog_4c925dca0100hsyt.html

http://blog.csdn.net/zhai56565/article/details/40503743

时间: 2024-10-25 19:12:15

疯狂Java学习笔记(53)------------Annotation(注释)第二篇的相关文章

疯狂Java学习笔记(87)-----------十篇必读的Java文章

1.Brian Goetz:"管理工作:发人深省的部分" 这其实不是一篇博文,而是Brian Goetz对于甲骨文公司Java的管理的一个非常有趣的讨论的记录.在 以前我们将Java语言与Scala或者Ceylon相比较的时候,对其1-2个特性一直稍微有些意见. 对于为什么Java尽快变得和其他语言一样"时髦"不是一个好主意,Brian提出了很好的观点.每一个Java开发者都应有所了解(大约一个小时). Youtube链接. 2.Aleksey Shipilёv:(

疯狂Java学习笔记(52)-----------Annotation(注释)第一篇

从JDK1.5开始,Java中增加了对元数据(MetaData)的支持,也就是Annotation(注释),这种Annotation与Java程序中的单行注释和文本注释是有一定区别,也有一定联系的.其实,我们现在说的Annotation是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理.通过Annotation,程序开发人员可以在不改变原来逻辑的情况下,在源文件嵌入一些补充的信息.代码分析工具,开发工具和部署工具可通过这些补充信息进行验正或者部署. Annotatio

疯狂java学习笔记之面向对象(八) - static和final

一.static: 1.static是一个标识符: - 有static修饰的成员表明该成员是属于类的; - 没有static修饰的成员表明该成员是属于实例/对象的. 2.static修饰的成员(Field.方法.初始化块),与类共存亡:static修饰的成员建议总是通过类名来访问,虽然它也可以通过实例来访问(实质也是通过类来访问的),所以平时若在其他程序中见到通过实例/对象来访问static成员时,可以直接将实例/对象 替换成类名: 3.程序都是先有类再有对象的,有可能出现有类但没有实例/对象的

【疯狂Java学习笔记】【理解面向对象】

[学习笔记]1.Java语言是纯粹的面向对象语言,这体现在Java完全支持面向对象的三大基本特征:封装.继承.多态.抽象也是面向对象的重要组成部分,不过它不是面向对象的特征之一,因为所有的编程语言都需要抽象. 2.面向对象开发方法比较结构化开发方法的优势在于可以提供更好的可重用性.可扩展性.可维护性. 3.基于对象和面向对象的区别:基于对象也使用了对象,但是无法通过现有的对象作为模板来生成新的对象类型,继而产生新的对象,也就是说,基于对象没有继承的特点.而面向对象有继承,而多态则是建立在继承的基

疯狂Java学习笔记(68)-----------synchronized

Java并发编程:synchronized Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块. 二.然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非syn

疯狂Java学习笔记(76)------------NIO.2第二篇

上一篇地址http://write.blog.csdn.net/postedit/46386609 在该系列的上一篇中我演示了NIO.2的三个方法:文件拷贝.文件和目录的删除和文件移动.在这篇文章中,我将向大家展示路径相关的方法(如获取路径.检索路径信息).文件和目录测试方法(如文件或目录的存在性测试)以及面向属性的方法. 获取路径 问:怎样获得一个 java.nio.file.Path 对象? 答:你可以调用 java.nio.file.Paths 类的下列任何一静态个方法,通过文件或目录在文

疯狂Java学习笔记(77)-----------注释注意事项

代码注释,可以说是比代码本身更重要.这里有一些方法可以确保你写在代码中的注释是友好的: 不要重复阅读者已经知道的内容 能明确说明代码是做什么的注释对我们是没有帮助的. // If the color is red, turn it green if (color.is_red()) { color.turn_green(); } 要注释说明推理和历史 如果代码中的业务逻辑以后可能需要更新或更改,那就应该留下注释:) /* The API currently returns an array of

疯狂Java学习笔记(84)----------关于 Java 对象序列化您不知道的 5 件事

数年前,当和一个软件团队一起用 Java 语言编写一个应用程序时,我体会到比一般程序员多知道一点关于 Java 对象序列化的知识所带来的好处. 关于本系列 您觉得自己懂 Java 编程?事实上,大多数程序员对于 Java 平台都是浅尝则止,只学习了足以完成手头上任务的知识而已.在本 系列 中,Ted Neward 深入挖掘 Java 平台的核心功能,揭示一些鲜为人知的事实,帮助您解决最棘手的编程挑战. 大约一年前,一个负责管理应用程序所有用户设置的开发人员,决定将用户设置存储在一个 Hashta

疯狂Java学习笔记(37)----------List集合

在网上找了很长时间关于List集合的资料,发现都是代码,理论性的东西太少了,对于想要深入的学习我来说,很困难呀,光看代码,不能解决问题呀!所以,自己精心的搜刮来了一点资料在这整理了一下! List集合! ·List列表 ·list: list代表有序.可重复集合,每个元素都有对应的索引,所以List集合中的元素可以重复.List集合默认暗元素的添加顺序设计元素! ·list当然也用于collection中的所有方法,并且自己也有添加了额外的方法,所有List实现类都可以调用这些方法来操作元素.L