8.4 自定义注解

  本节会介绍自定义注解。不过要想让自定义注解真正起作用,必须要了解Java提供的四个元注解(用于修饰注解的注解)@Target、@Retention、@Documented和@Inherited。

8.4.1 自定义注解

  注解之所以强大,能被众多框架所使用的主要原因在于,它可以允许程序员自定义注解,使Java程序变成自描述的。注解的语法形式和接口差不多,只是在interface前面多了一个“@”符号。

  1. public @interface MyAnnotation{
  2. }

  上面的代码是一个最简单的注解,这个注解没有属性。我们可以在自定义注解时定义属性,在注解类型的定义中以无参方法的形式来声明,其方法名和返回值分别定义了该属性的名字和类型,其代码如下:

  1. public @interface MyAnnotation{
  2. //定义一个属性value
  3. String value();
  4. }

  可以按如下格式使用MyAnnotation注解:

  1. public class TestAnnotation{
  2. //如果没有写属性名,而这个注解又有value属性,则将这个值赋给value属性
  3. //@MyAnnotation("good")
  4. @MyAnnotation(value = "good")
  5. public void getObjectInfo(){
  6. }
  7. }

  接下来修改自定义注解MyAnnotation,使其含两个属性,具体代码如下:

  1. public @interface MyAnnotation{
  2. //定义两个属性name和age
  3. String name();
  4. int age();
  5. }

  在注解中可以定义属性,也可以给属性赋默认值,具体代码如下:

  1. public @interface MyAnnotation{
  2. //定义带默认值的属性
  3. String name() default "姓名";
  4. int age() default 22;
  5. }

  定义了注解之后,接下来就可以在程序中使用注解,具体代码如下:

  1. public class TestAnnotation{
  2. //使用带属性的注解时,需要为属性赋值
  3. @MyAnnotation(name = "柳海龙",age = 24)
  4. //@MyAnnotation
  5. public void getObjectInfo(){
  6. }
  7. }

  请注意注释的描述,使用带属性的注解时,需要给属性赋值。不过如果在定义注解时给属性赋了默认值,则可使用不带属性值的注解,也就是让注解使用自己的默认值。

8.4.2 元注解

  上一小节虽然学习了自定义注解,但是肯定有不少人觉得学完之后,心里空空的,不知道自定义注解到底有什么用?下面的内容就是来解决这个问题的。

  在自定义注解时,注解看起来和类、接口比较类似,尤其看起来更像接口。注解可以理解为和接口一样,是程序的一个基本组成部分。既然可以对类、接口、方法和属性等进行注解,那么当然也可以对注解进行注解。

  使用不同注解对注解进行注解的方法,和对类、接口进行注解的方法一样,所不同的是,Java为注解单独提供了四种元注解,即@Target、@Retention、@Documented和@Inherited,下面分别介绍这四种元注解。

8.4.3 @Target注解

  @Target元注解很容易理解,Target中文含义为目标,使用@Target注解的目的是用于指定被修饰的注解能用于修饰哪些程序元素。如果注解定义中不存在@Target元注解,则此注解可以用在任一程序元素上,如果存在这样的元注解,则编译器强制实施指定的使用限制。

  此注解类型有唯一的value作为成员变量,其定义为public abstract ElementType[] value。

  接下来看这样一个案例,将之前自定义的注解用@Target进行注解,以限制此注解只能使用在属性上。此时如果将此注解使用在方法上,编译器会报出“注释类型不适用于该类型的声明”的错误。案例具体代码如下:

  1. import java.lang.annotation.*;
  2. //限制此注解只能使用在属性上
  3. @Target({ElementType.FIELD})
  4. public @interface MyAnnotation{
  5. String name() default "姓名";
  6. int age() default 22;
  7. }
  8. public class TestAnnotation{
  9. //在方法上使用自定义注解
  10. @MyAnnotation
  11. public void getObjectInfo(){
  12. }
  13. }

  @Target注解的属性value可以为以下值,被@Target注解的注解只能用来注解对应的目标。

  • ElementType.ANNOTATION_TYPE:注解类型声明。

  • ElementType.CONSTRUCTOR:构造方法声明。

  • ElementType.FIELD:字段声明(包括枚举常量)。

  • ElementType.LOCAL_VARIABLE:局部变量声明。

  • ElementType.METHOD:方法声明。

  • ElementType.PACKAGE:包声明。

  • ElementType.PARAMETER:参数声明。

  • ElementType.TYPE:类、接口(包括注解类型)或枚举声明。

8.4.4 @Retention注解

  @Retention 元注解用于指定被修饰的注解可以保留多长时间。如果注解定义中不存在 @Retention元注解,则保留策略默认为RetentionPolicy.CLASS。

  @Retention包含一个RetentionPolicy类型的value属性,使用此注解时必须为该value属性指定值。@Retention注解的属性value允许的值及含义如下。

  • RetentionPolicy.CLASS:编译器将把注解记录在class文件中,当运行Java程序时,虚拟机不再保留注解。

  • RetentionPolicy.RUNTIME:编译器将把注解记录在class文件中,当运行Java程序时,虚拟机保留注解,程序可以通过反射获取该注解。

  • RetentionPolicy.SOURCE:编译器将直接丢弃被修饰的注解。

  接下来通过一个案例来演示通过反射获取注解,具体代码如下。案例中提供了较为详细的注释,请大家认真阅读。

  1. import java.lang.annotation.*;
  2. @Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD
  3. ,ElementType.PARAMETER,ElementType.CONSTRUCTOR,ElementType.LOCAL_VARIABLE})
  4. //当运行Java程序时,虚拟机保留注解
  5. @Retention(RetentionPolicy.RUNTIME)
  6. public @interface MyAnnotation{
  7. String name() default "姓名";
  8. int age() default 22;
  9. }
  10. import java.lang.annotation.Annotation;
  11. public class TestAnnotation{
  12. public static void main(String[] args) throws SecurityException,
  13. NoSuchMethodException, ClassNotFoundException {
  14. TestAnnotation ta = new TestAnnotation();
  15. ta.getObjectInfo();
  16. }
  17. @MyAnnotation
  18. @Deprecated
  19. public void getObjectInfo() throws ClassNotFoundException,
  20. SecurityException, NoSuchMethodException{
  21. //利用反射机制获取注解
  22. Annotation[] arr = Class.forName("TestAnnotation")
  23. .getMethod("getObjectInfo").getAnnotations();
  24. //遍历每个注解对象
  25. for(Annotation an:arr){
  26. if(an instanceof MyAnnotation){//如果注解是MyAnnotation类型
  27. System.out.println("MyAnnotation注解:" + an);
  28. System.out.println("MyAnnotation注解的name属性值:"+((MyAnnotation)an).
  29. name());
  30. System.out.println("MyAnnotation注解的age属性值:"+((MyAnnotation)an).age());
  31. }else{
  32. System.out.println("非MyAnnotation注解:" + an);
  33. }
  34. }
  35. }
  36. }

  代码中getObjectInfo()方法有两个注解,@MyAnnotation和@Deprecated,其中,自定义注解@MyAnnotation的元注解@Retention的值为RetentionPolicy.RUNTIME,含义为当运行Java程序时,虚拟机保留注解,所以在运行时可以通过反射机制获取该注解。程序运行结果如图8.3所示。@Deprecated为内建注解,通过运行结果可以看出@Deprecated的元注解@Retention的值也是RetentionPolicy.RUNTIME。

8.4 自定义注解 - 图1


图8.3 通过反射机制获取注解

8.4.5 @Documented注解

  一看这个元注解的名字,就知道它一定和文档有关。在默认的情况下使用javadoc工具自动生成文档时,注解将被忽略掉。如果想在文档中也包含注解,必须使用@Documented 为文档注解。@Documented注解类型中没有成员变量。如果定义注解时使用了@Documented修饰,则所有使用该注解修饰的程序元素的API文档中都将包含该注解说明。

  1. //@Documented
  2. public @interface MyAnnotation{
  3. String name() default "姓名";
  4. int age() default 22;
  5. }
  6. @MyAnnotation
  7. public class TestAnnotation{
  8. }

  使用javadoc生成文档,产生的文档对TestAnnotation类的描述如下:

  1. class TestAnnotation extends java.lang.Object

  如果取消对@Documented的注释,使其起作用,将会出现另一个结果:

  1. @MyAnnotation
  2. class TestAnnotation extends java.lang.Object

8.4.6 @Inherited注解

  继承是面向对象的特性之一,Java是典型的面向对象语言,所以继承是Java语言典型的特性。前面讲过,注解是程序的一个基本组成部分,那么父类的注解是否被子类继承呢?默认情况下,父类的注解不被子类继承,如果要想继承父类注解,就必须使用@Inherited元注解。接下来通过下面的三小段代码,介绍@Inherited注解的含义。

  1. import java.lang.annotation.*;
  2. @Inherited
  3. public @interface MyAnnotation{
  4. String name() default "姓名";
  5. int age() default 22;
  6. }
  7. @MyAnnotation
  8. public class Vehicle
  9. {
  10. public void drive()
  11. {
  12. //省略若干代码
  13. }
  14. }
  15. public class Truck extends Vehicle
  16. {
  17. //省略若干代码
  18. }

  通过以上的代码,Truck类和Vehicle类一样都被@MyAnnotation注解了。