二.自定义Annotation
前面已经介绍了如何使用java.lang包下的三个标准Annotation。下面介绍如何定义 Annotation,并利用Annotation完成一些实际的功能。
(1)定义Annotation
定义新的Annotation类型时使用@interface关健字(在原有的interface关健字前增加@符号),它用于定义新的Annotation类型。定义一个新的Annotation类型与定义一个接口非常像。
如下代码定义一个简单的Annotation
//定义一个简单的Annotation类型
public @interface Test
{
}
定义该Annotation之后,就可以在程序任何地方使用该Annotation ,使用Annotation时的语法非常类似于public ,final这样的修饰符,通常可以用于修饰程序中的类,方法,变量,接口等定义。
通常我们会把Annotation放在所有修饰符之前,而且由于使用Annotation时可能还需要为其它成员变量指定值,因而Annotation的长度可能较长,所以通常把Annotaion另放一行.
如
//使用@Test修饰类定义
@Test
public class MyClass
{
...
}
默认情况下,Annotation可用于修饰任何元素,包括类,接口,方法等。如下程序使用@TestAnnotation来修饰方法
public class MyClass
{
//使用@TestAnnotation修饰方法
@Test
public void info()
{
...
}
}
Annotation不仅可以是这种简单的Annotation,Annotation还可以带用员变量,Annotation的成员变量在Annotation定义中以无参数来声明。其方法和返回值定义了该成员的名字和类型。
如下代码可以定义一个有成员变量的Annotation
public @interface MyTag
{
//定义两个成员变量Annotation
//Annotation中的成员变量以方法的形式来定义
String name();
int age();
}
仔细观察:上面定义Annotation的代码与定义接口的语法非常像,只是上面MyTag使用@interface关健字来定义,而接口使用interface来定义
一旦在Annotation里定义了成员变量之后,使用该Annotation时应该为该Annotation的成员变量指定值,如下代码所示
public class Test
{
//使用带成员变量的Annotation时,需要为成员变量付值
MyTag(name='heyitang',age=30)
public void info()
{
...
}
}
我们还可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用default关健字。如下代码定义了MyTag Annotaion,该Annotation里包含了两个成员:name和age,这两个成元变量使用default指定了默认值
public @interface MyTag
{
//定义了两个成员变量的Annotaion
//以default为两个成员变量指定初始值
String name() default "yeeku";
int age() default 32;
}
如果为Annotation的成员变量指定了默认值,使用该Annotation则可以不为这些成员变量指定值,而是直接使用默认值,如下代码所示
public class Test
{
//使用带成员变量的Annotation
//因为它的成员变量有默认值,所以可以无须为成员变量指定值
@MyTag
public void info()
{
...
}
}
当然我们介绍的Annotation是否可以包含成员指定值,如果MyTag为的成员变量指定了值,则默认值不会起作用
总结
根据我们介绍的Annotation是否可以包含成员变量,我们可以把Annotation分为如下两类
1.标记Annotation:一个没有成员变量的Annotation类型被称为标记。这种Annotation仅合使用自身的存在与否来为我们提供信息。如前面介绍的@Override,@Test等Annotation
2.元数据Annotation:那些包含成员变量的Annotation,因为它们可接受更多元数据,所以也被称为元数据Annotation
(2)提取Annotation
前面已经提到:JAVA使用Annotation接口来代表元素前面的注释,该接品是所有Annotation类型的父接口。除此之外,JAVA在java.lang.reflect包下新增了AnnotationElement接口,该接口代表程序中可以接受注释的程序元素,该接口主要有如下几个实现类.
Class:类定义
Constructor:构造器定义
Field:类的成员变量定义
Method:类的方法定义
Package:类的包定义
AnnotationElement接口是所有程序元素(如Class,Method,Constructor)的父接口,所以程序通过反射获取某个类的了AnnotationElement对象(如Class,Method,和Constructor)之后,程序就可以调用该对象的如下三个方法来访问Annotation信息
getAnnotation(Class<T> annotationClass):返回该程序元素上存在的,指定类型的注释
Annotation[] getAnnotations:返回该程序元素上存在的所有注释
boolean isAnnotationPreset(Class<? extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注释,存在则返回true,否则返回false
(3)使用Annotation的例子
下面介绍两个使用Annotation的例子,第一个Annotation Testable没有任何成员变量,仅是一个标记Annotation,它的作用是标记哪些方法是可测试的.
Testable.java源文件
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
//定义Testable Annotation将被javadoc工具提取
@Documented
public @interface Testable
{
}
上面程序定义了一个标记Testable Annotation,定义该Annotation时使用了@Retention和@Target两个系统元注释,其中@Retention注释指明Testabel注释可以保留多久,而@Target注释指定Testable能修饰的目标(只能是方法)。
如下MyTest测试用例里定义了8个方法,这8个方法没有太大的区别,其中四个方法使用@testable注释来标记这些方法是可测试的
public class MyTest
{
//使用@Testable标记注释指定该方法是可测试的
@Testable
public static void m1()
{
}
public static void m2()
{
}
//使用@Testable标记注释指定该方法是可测试的
@Testable
public static void m3()
{
throw new RuntimeException("Boom");
}
public static void m4()
{
}
//使用@Testable标记注释指定该方法是可测试的
@Testable
public static void m5()
{
}
public static void m6()
{
}
//使用@Testable标记注释指定该方法是可测试的
@Testable
public static void m7()
{
throw new RuntimeException("Crash");
}
public static void m8()
{
}
}
正如前面提到的,仅仅使用注释来标记程序元素对程序是不会有任何影响的,这也是注释的一条重要原则,为了让程序中这些注释起作用,我们必须为这些注释提供一个注释处理工具。
下面的注释处理工具分析目标类,如果目标类中方法使用了@Testable注释修饰,则通过反射来运行该测试方法
import java.lang.reflect.*;
public class TestProcessor
{
public static void process(String clazz)
throws ClassNotFoundException
{
int passed = 0;
int failed = 0;
//遍历obj对象的所有方法
for (Method m : Class.forName(clazz).getMethods())
{
//如果包含@Testable标记注释
if (m.isAnnotationPresent(Testable.class))
{
try
{
//调用m方法
m.invoke(null);
//passed加1
passed++;
}
catch (Exception ex)
{
System.out.printf("方法" + m + "运行失败,异常:" + ex.getCause() + "\n");
failed++;
}
}
}
//统计测试结果
System.out.printf("共运行了:" + (passed + failed)+ "个方法,其中:\n" +
"失败了:" + failed + "个,\n" +
"成功了:" + passed + "个!\n");
}
}
该TestProcessor类里只包含了一个process方法,该方法可接受一个字符串参数,该方法将会分析clazz参数所代表的类,并运行该类里的,使用@Testabel注释修饰的方法
该程序的主类比较简单,只提供主方法
import java.lang.reflect.*;
public class RunTests
{
public static void main(String[] args) throws Exception
{
//处理MyTest类
TestProcessor.process("MyTest");
}
}
运行最终效果图如下
![Java中Annotation(注释)系列学习笔记(2) Java中Annotation(注释)系列学习笔记(2)](https://i-blog.csdnimg.cn/blog_migrate/7a860a4bf3f149415d70a11cedc9ac76.jpeg)
总结
通过这个运行结果可以看出,程序中的@Testable Annotation起作用了,MyTest类里以@Testable注释修饰的方法被正常测试了
Java中Annotation(注释)系列学习笔记(3)
三.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工具提取
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
//定义Testable Annotation将被javadoc工具提取
@Documented
public @interface Testable
{
}
上面程序中的@Documented代码决定了所有使用@Testable Annotation的地方都会被javadoc工具提取到api文档中
下面程序中定义了一个 MyTest类,该类中的infor方法使用Testable Annotation修饰
程序清单
public class MyTest
{
//使用@Testable修饰info方法
@Testable
public void info()
{
System.out.println("info方法...");
}
}
使用javadoc工具为Testable.java ,MyTest文件生成api文档后如下面所示
![Java中Annotation(注释)系列学习笔记(3) Java中Annotation(注释)系列学习笔记(3)](https://i-blog.csdnimg.cn/blog_migrate/dc1142c6963a3ed1fb1c56afae840d6c.jpeg)
如果,我们去掉不用@Documented修饰Testable的话,则不会被javadoc提取
(4)使用@Inherited
@Inherited元Annotation指定被它修饰的Annotation将具有继承性。如果某个类使用了Annotaion(使用Annotation时使用了@Inherited修饰)修饰,则其子类将自动具有A注释。
下面使用@Inherited元数据注释定义了一个Inherited Annotation,该Annotation将具有继承性
程序清单
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
// @Inherited
public @interface Inheritable
{
}
上面程序中表明了@Inheritable Annotation具有继承性,如果某个类使用了该Annotation 修饰,则该类的子类将自动具有@Inheritable Annotation
下面程序定义了一个Base基类,该基类使用了@Inherited修饰,则Base类的子类将自动具有@Inherited Annotation
程序清单
//使用@Inheritable修饰的Base类
@Inheritable
class Base
{
}
//TestInheritable类只是继承了Base类,
//并未直接使用@Inheritable Annotiation修饰
public class TestInheritable extends Base
{
public static void main(String[] args)
{
//打印TestInheritable类是否具有Inheritable Annotation
System.out.println(TestInheritable.class.isAnnotationPresent(Inheritable.class));
}
}
运行效果图如下
![Java中Annotation(注释)系列学习笔记(3) Java中Annotation(注释)系列学习笔记(3)](https://i-blog.csdnimg.cn/blog_migrate/8ad84853bb5eb52192b221598209444d.jpeg)
总结
1.上面程序中的Base类使用了@Inheritable Annotation修饰,而该Annotaion具有可继承性,所以其子类也将具有@Inheritable Annotation,运行上面程序看到输出 true
2.如果将上面的Inheritable.java程序中的@Inherited注释或者删除,将会导致Inheritable Annotation不具有继承性,运行上面程序将会输出 false
Java中Annotation(注释)系列学习笔记(4)
(四)使用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类型,分别用于修饰持久化类,标识属性和普通属性。
程序清单
修饰表属性
import java.lang.annotation.*;
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Persistent
{
String table();
}
修饰标识属性
import java.lang.annotation.*;
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface IdProperty
{
String column();
String type();
String generator();
}
修饰普通成员变量的Annotation
import java.lang.annotation.*;
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Property
{
String column();
String type();
}
定义了三个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);
}
}
提供了上面的处理器工厂后,就可以使用APT工具来处理上面的Person.java源文件,并根据该源文件来生成一个XML文件。 APT工具位于JDK的安装路径的bin路径下。。
运行APT命令时,可以使用-factory选项来指定处理器工厂类
如下所示
rem 使用HibernateAnnotationFactory作为处理器工厂来处理Person.java中的Annotation
apt -factory HibernateAnnotationFactory Person.java
使用APT工具,HibernateAnnotationFactory工厂来处理Person.java后,将可以看到在相同路径下,生成了一个Person.hbm.xml文件了,该文件内容如下
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="Person" table="persons_table">
<id name="id" column="person_id" type="integer">
<generator class="identity"/>
</id>
<property name="name" column="person_name" type="string"/>
<property name="age" column="person_age" type="integer"/>
</class>
</hibernate-mapping>
![Java中Annotation(注释)系列学习笔记(4) Java中Annotation(注释)系列学习笔记(4)](https://i-blog.csdnimg.cn/blog_migrate/a4c26d1e5885305701be709a3d33442f.gif)
总结
通过上面生成的xml文件,我们可以看出,通过使用APT工具确实可以简化程序开发,程序员只需把一些关键信息通过Annotation写在程序中,然后使用APT工具就可生在额外的文件。