zoukankan      html  css  js  c++  java
  • Java程序设计16——Annotatio注释

      Annotation是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用Annotation,程序开发人员可以在不改变原有逻辑的情况下,在源文件嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。 
      Annotation提供了一种为程序元素设置元数据的方法,从某些方面来看,Annotation就像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在Annotation的"name=value"对中。
      注意:Annotation是一个接口,程序可以通过反射来获取指定程序元素的Annotation对象。然后通过Annotation对象来取得注释里的元数据。需要注意本章中使用Annotation的地方,有的Annotation指的是java.lang.Annotation接口,有的指的是注释本身。
      Annotation能被用来作为程序元素(类、方法、成员变量等)设置元数据。需要指出的是:Annotation不能影响程序代码的执行,无论增加、删除Annotation,代码都始终如一地执行。如果希望让程序中的Annotation能在运行时起一定的作用,只有通过某种配套的工具对Annotation中的信息进行访问和处理,访问和处理Annotation的工具统称为APT(Annotation Processing Tool)。

    1 基本Annotation                                                    

      Annotation必须使用工具来处理,工具负责提取Annotation里包含的元数据,工具还会根据这些元数据增加额外的功能。在系统学习新的Annotation语法之前,先看一下Java提供的三个基本Annotation的用法:使用Annotation时要在其前面增加@符号,并把该Annotation当成一个修饰符使用,用于修饰它支持的程序元素。
    三个基本的Annotation如下

    1.@Override
    2.@Deprecated
    3.@SuppressWarnings

    限定重写父类方法:@Override
    @Override就是用来指定方法覆写的,它可以强制一个子类必须要覆写父类的方法。如下程序中使用@Override指定子类Apple的info方法必须重写父类方法。

     1 package chapter16;
     2 
     3 class Fruit {
     4     public void foo(){
     5         System.out.println("水果的info方法....");
     6     }
     7 };
     8 public class Apple extends Fruit{
     9     //使用@Override指定下面的方法必须重写父类方法
    10     @Override
    11     public void foo(){
    12         System.out.println("苹果重写水果的info方法....");
    13     }
    14     public static void main(String[] args){
    15         Fruit f = new Apple();
    16         f.foo();
    17     }
    18 }
    19 
    20 输出结果:
    21 苹果重写水果的info方法....

      编译上面程序,可能看不出程序中的@Override有何作用,因为@Override Annotation的作用是告诉编译器检查这个方法,并从父类查找是包含一个被该方法重写的方法,否则就编译出错。这个Annotation主要是帮助我们避免一些低级错误。例如我们把上面的Apple类的info方法不小心写成inf()这样的低级错误,可能会导致后期排错时的巨大障碍。但是如果你没有调用重写的方法,那就是调用了父类的方法,这样产生的结果就是另一种结果,编译会通过,但是结果是不符合预期的。

     1 package chapter16;
     2 
     3 class Fruit {
     4     public void foo(){
     5         System.out.println("水果的info方法....");
     6     }
     7 };
     8 public class Apple extends Fruit{
     9     //使用@Override指定下面的方法必须重写父类方法
    10     @Override
    11     public void fool(){
    12         System.out.println("苹果重写水果的info方法....");
    13     }
    14     public static void main(String[] args){
    15         Fruit f = new Apple();
    16         f.foo();
    17     }
    18 }
    19 
    20 预期结果:水果的info方法....

    标示已过时:@Deprecated
    @Deprecated用于表示某个程序元素(类、方法等)已过时,当其他程序使用已过时的类、方法时,编译器将会给警告。如下程序指定Apple类中的info方法已过时,其他程序中使用Apple类的info方法时编译器将会给出警告。

    那些是被@Deprecated注解标记的方法或者属性或类等,意思是“已过时”。如果你是新写代码,那么不推荐你这么做,有更好的替代方案,如果是老系统,那么告知你你这个方法已过时,不过JDK还将继续对他支持。被注解掉@Deprecated,表示是方法过时有新的方法替代,在jdk文档中可以找到相对应的新方法。

     

    可以看到上面被deprecated的方法被划上了横线

    上面应用程序使用了一个被deprecated的方法,表名该方法以及过时,在新版的jdk有替代的更好的方法。

    抑制编译器警告:@SuppressWarnings
    @SuppressWarnings指示被Annotation标识的程序元素(以及在该程序元素中的所有子元素)取消显示指示的编译器警告。@SuppressWarnings会一直作用于该程序元素的所有子元素,例如使用@SuppressWarnings标识一个类来取消显示某个编译器警告,同时又标识该类里某个方法取消显示另一个编译器警告,那么将在此方法同时取消这两个编译器警告。
    通常情况下,如果程序中使用没有泛型限制的集合将会引起编译器警告,为了避免这种编译器警告,可以使用@SuppressWarningsAnnotation,下面程序取消了没有使用泛型的编译器警告。

     1 package chapter16;
     2 
     3 import java.util.*;
     4 
     5 @SuppressWarnings(value="unchecked");
     6 
     7 public class SuppressWarningsTest {
     8     public static void main(String[] args){
     9         List<String> l = new ArrayList();
    10     }
    11 }

    程序使用@SuppressWarnings来关闭SuppressWarningsTest类里的所有编译器警告,编译上面程序时将不会砍掉任何编译器警告。
    注意:使用@SuppressWarnings Annotation来关闭编译器警告时,一定需要在括号里使用name=value对来为该Annotation的成员变量设置值。

    2 自定义Annotation                                              

      上面介绍的3个Annotation是java.lang包下的三个标准Annotation,下面介绍自定义的Annotation,并利用Annotation完成一些实际功能。

    2.1 定义Annotation         

      定义新的Annotation类型使用@interface关键字(在原有的interface关键字前增加@符号),它用于定义新的Annotation类型。定义一个新的Annotation类型与定义一个接口非常像。如下即可定义一个简单的Annotation

    1 //定义一个简单的Annotation类型
    2 public @interface Test{
    3 
    4 }

      定义了该Annotation之后,就可以在程序任何地方来使用该Annotation,使用Annotation时的语法非常类似于public、final这样的修饰符。通常可用于修饰程序中的类、方法、变量、接口等定义,通常我们会把Annotation放在所有修饰符之前,而且由于使用Annotation时可能还需要为其成员变量指定值,因而Annotation的长度可能较长,所以通常把Annotation另放一行。如下所示。

    //使用@Test修饰类定义
    @Test
    public class MyClass{
        .....
    }

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

    1 public class MyClass{
    2     //使用@Test Annotation修饰方法
    3     @Test
    4     public void info(){
    5         .....
    6     }
    7     .....
    8 }

      Annotation不仅可以是这种简单的Annotation,Annotation还可以带成员变量,Annotation的成员变量在Annotation定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。如下代码可以定义一个有成员变量的Annotation:

    1 package chapter10;
    2 
    3 public @interface MyTag {
    4     //定义了两个成员变量的Annotation
    5     //Annotation的成员变量定义方式类似于传统的方法
    6     String name();
    7     int age();
    8 }

      上面的注释和接口定义是很像的,注释使用@interface来定义,而接口用interface定义。但是对于变量的定义是有点区别的,上面定义的是变量,但不是方法。一旦在Annotation里定义了成员变量之后,使用该Annotation时应该为该Annotation的成员变量指定值,如下所示:

    1 public class Test{
    2     //使用带成员变量的Annotation时,需要为成员变量赋值
    3     @MyTag(name="xx", age=6)
    4     public void info(){
    5         .....
    6     }
    7 .....
    8 }

      我们还可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用default关键字。如下代码定义了MyTag Annotation,该Annotation里包含了两个成员变量:name和age,这两个成员变量使用了default指定了默认值。

     1 package chapter10;
     2 
     3 public @interface MyTag {
     4     //定义了两个成员变量的Annotation
     5     //Annotation的成员变量定义方式类似于传统的方法
     6     String name() default "yeeku";
     7     int age() default 3;
     8 }
     9     如果为Annotation的成员变量指定了默认值,使用该Annotation则可以不为这些成员变量指定值,而是直接使用默认值,如下代码所示:
    10     public class Test{
    11         //使用带成员变量的Annotation
    12         //因为它的成员变量有默认值,所以可以无须为成员变量指定值
    13         @MyTag
    14         public void info(){
    15         ...
    16         }
    17         ....
    18     }

      当然我们也可以在使用MyTagAnnotation时为成员变量指定值,如果为MyTag的成员变量指定了值,则默认值不起作用。根据我们介绍的Annotation是否可以包含成员变量,可以把Annotation分成两类:
    1.标记Annotation:一个没有成员定义的Annotation类型被称为标记。这种Annotation仅使用自身的存在与否来为我们提供信息。如前面介绍的@Override、@Test等Annotation
    2.元数据Annotation,那些包含成员变量的Annotation。因为它们可接受更多元数据,所以也被称为元数据Annotation

    2.2 提取Annotation的信息 

      Java使用Annotation接口来代表程序元素前面的注释,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect包下新增了AnnotatedElement接口,该接口代表程序中可以接收注释的程序元素,该接口主要由如下几个实现类:
    Class:类定义
    Constructor:构造器定义
    Field:类的成员变量定义
    Method:类的方法定义
    Package:类的包定义
      java.lang.reflect包下主要包含了一些实现反射功能工具类,实际上,java.lang.reflect包所提供的反射APL扩充了读取运行时Annotation的能力。当一个Annotation类型被定义为运行时Annotation后,该注释才是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
      AnnotatedElement接口是所有程序元素(如Class、Method、Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象如Class、Method、Constructor之后,程序就可以调用该对象的如下三个方法来访问Annotation信息:

    1.getAnnotation(Class<T> annotationClass):返回该程序元素上存在的、指定类型的注释,如果该类型的注释不存在,则返回null。
    2.Annotation[] getAnnotation():返回该程序元素上存在的所有注释
    3.boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注释,存在则返回true,否则返回false
    View Code

      为了获取程序中程序元素如Class、Method等,必须使用反射知识。

    下面程序片段获取Test类的info方法里的所有注释,并将这些注释打印出来:

     1 //获取Test类的info方法的所有注释
     2     Annotation[] aArray = Class.forName("Test").getMethod("info").getAnnotations();
     3     //遍历所有注释
     4     for(Annotation an : aArray){
     5         System.out.println(an);
     6     }
     7     如果我们需要获取某个注释里的元数据,则可以将注释强制类型转换成所需的注释类型,然后通过注释对象的抽象方法来访问这些元数据,如下所示:
     8     //获取tt对象的info方法所包含的所有注释
     9     Annotation[] annotation = tt.getClass().getMethod("info").getAnnotation();
    10     //遍历每个注释对象
    11     for(Annotation tag:annotation){
    12         //如果tag注释是MyTag1类型
    13         if(tag instanceof MyTag1){
    14             System.out.println("Tag is: " + tag);
    15             //将tag强制类型转换为MyTag1,
    16             //并调用tag对象的method1和method2两个方法
    17             System.out.println("tag.name(): " + ((MyTag1)tag).method1());
    18             System.out.println("tag.age(): " + ((MyTag1)tag).method2());
    19         }
    20         //如果tag注释是MyTag2类型
    21         if(tag instanceof MyTag2){
    22             System.out.println("Tag is: " + tag);
    23             //将tag强制类型转换为MyTag2,
    24             //并调用tag对象的method1和method2两个方法
    25             System.out.println("tag.name(): " + ((MyTag2)tag).method1());
    26             System.out.println("tag.age(): " + ((MyTag2)tag).method2());
    27         }
    28     }

    使用Annotation的例子
      下面介绍两个使用Annotation的例子,第一个Annotation Testable没有任何成员变量,仅是一个标记Annotation,它的作用是标记哪些方法是可测试的。

     1 package chapter10;
     2 
     3 import java.lang.annotation.ElementType;
     4 import java.lang.annotation.Retention;
     5 import java.lang.annotation.RetentionPolicy;
     6 import java.lang.annotation.Target;
     7 
     8 //使用JDK的元数据Annotation:Retention
     9 @Retention(RetentionPolicy.RUNTIME)
    10 //使用JDK的元数据Annotation:Target
    11 @Target(ElementType.METHOD)
    12 public @interface Testable{
    13         
    14 }

      上面程序定义了一个标记TestableAnnotation,定义该Annotation时使用了@Retention和@Target两个系统元注释,其中@Retention注释指定Testable注释可以保留多久,而@Target注释指定Testable注释能修饰的目标(只能是方法)。

      上面的Testable Annotation用于标识哪些方法是可测试的,该Annotation可以作为JUnit测试框架的补充,在JUnit框架中它要求测试用例的测试方法必须以test开头。如果使用Testable注释则可把任何方法标记为可测试的。
      如下MyTest测试用例里定义了8个方法,这8个方法没有太大的区别,其中4个方法使用@Testable注释来标记这些方法是可测试的。

     1 public class MyTest{
     2     //使用@Testable标记注释指定该方法是可测试的
     3     @Testable
     4     public static void m1(){
     5     }
     6     public static void m2(){
     7     }   
     8     //使用@Testable标记注释指定该方法是可测试的
     9     @Testable
    10     public static void m3(){        
    11         throw new RuntimeException("Boom");  
    12     }
    13     public static void m4(){
    14     }       
    15     //使用@Testable标记注释指定该方法是可测试的
    16     @Testable
    17     public static void m5(){
    18     }  
    19     public static void m6(){
    20     }
    21     //使用@Testable标记注释指定该方法是可测试的
    22     @Testable
    23     public static void m7(){            
    24         throw new RuntimeException("Crash");   
    25     }        
    26     public static void m8(){
    27     }
    28 }

      如前所述,仅仅使用注释来标识程序元素对程序是不会有任何影响的,这也是Java注释的一条重要原则,为了让程序中这些注释起作用,我们必须为这些注释提供一个注释处理工具。
    下面的注释处理工具会分析目标类,如果目标类中方法使用了@Testable注释修饰,则通过反射来运行该测试方法

     1 package chapter10;
     2 
     3 
     4 import java.lang.reflect.*;
     5 
     6 public class TestProcessor{
     7     public static void process(String clazz)
     8         throws ClassNotFoundException{
     9         int passed = 0;
    10         int failed = 0;
    11         //遍历obj对象的所有方法
    12         for (Method m : Class.forName(clazz).getMethods()){
    13             //如果包含@Testable标记注释
    14             if (m.isAnnotationPresent(Testable.class)){
    15                 try{
    16                     //调用m方法
    17                     m.invoke(null);
    18                     //passed加1
    19                     passed++;
    20                 }
    21                 catch (Exception ex){
    22                     System.out.printf("方法" + m + "运行失败,异常:" + ex.getCause() + "
    ");
    23                     failed++; 
    24                 }
    25             }
    26         }
    27         //统计测试结果
    28         System.out.printf("共运行了:" + (passed + failed)+ "个方法,其中:
    " + 
    29             "失败了:" + failed + "个,
    " +  
    30             "成功了:" + passed + "个!
    "); 
    31     }
    32 }

      TestProcessor类里只包含一个process方法,该方法可接受一个字符串参数,该方法将会分析clazz参数所代表的类,并运行该类里的、使用了@Testable注释修饰的方法。
      该程序的主类非常简单,提供主方法,使用TestProcessor来分析目标类即可。

     1 package chapter10;
     2 
     3 import java.lang.reflect.*; 
     4 
     5 public class RunTests{
     6     public static void main(String[] args) throws Exception{
     7         //处理MyTest类
     8         TestProcessor.process("MyTest");
     9     }
    10 }

      通过这个运行结果可以看出,程序中的@Testable Annotation起作用了,MyTest类里以@Testable注释修饰的方法被正常测试了。
      通过上面例子可以看出,JDK的注释很简单,我们为源代码中添加一些特殊标记,这些特殊标记可通过反射获取,一旦程序访问到这些标记后,程序就可以做出相应的处理。

    3 JDK的元Annotation                            

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

    3.1 使用@Retention           

      @Retention只能用于修饰一个Annotation定义,用于指定该Annotation可以保留多长时间,@Retention包含一个RetentionPolicy类型的value成员变量,所以使用@Retention时必须为该value成员变量指定值。
    value成员变量的值只能是如下三个:
    1.RetentionPolicy.CLASS:编译器将把注释记录在class文件中。当运行Java程序时,JVM不再保留注释,这是默认值
    2.RetentionPolicy.RUNTIME:编译器把注释记录在class文件中。当运行Java程序时,JVM也会保留注释,程序可以通过反射获取该注释
    3.RetentionPolicy.SOURCE:编译器直接丢弃这种策略的注释。
      在前面程序中因为我们要通过反射获取注释信息,所以我们制定value属性值为RetentionPolicy.RUNTIME。使用@Retention元数据Annotation可采用如下代码为value指定值:

    //定义下面的Testable Annotation的保留到运行时
    @Retention(value=RetentionPolicy.RUNTIME)
    public @interface Testable{}

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

    //定义下面的Testable Annotation将被编译器直接丢弃
    @Retention(RetentionPolicy.SOURCE)
    public @interface Testable{}

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

    3.2 使用@Target             

    @Target也是用于修饰一个Annotation定义,它用于指定被修饰的Annotation能用于修饰哪些程序元素。@Target Annotation也包含一个名为value的成员变量,该成员变量的值只能是如下几个:
    1.ElementType.ANNOTATION_TYPE:指定该策略的Annotation只能修饰Annotation
    2.ElementType.CONSTRUCTOR:指定该策略的Annotation能修饰构造器
    3.ElementType.FIELD:指定该策略的Annotation只能修饰成员变量
    4.ElementType.LOCAL_VARIABLE:指定该策略的Annotation只能修饰具备变量
    5.ElementType.METHOD:指定该策略的Annotation只能修饰方法定义
    6.ElementType.PACKAGE:指定该策略的Annotation只能修饰包定义
    7.ElementType.PARAMETER:指定该策略的Annotation可以修饰参数
    8.ElementType.TYPE:指定该策略的Annotation可以修饰类、接口(包括注释类型)或枚举定义
      与使用@Retention类似的是,使用@Target也可以直接在括号里指定value值,可以无须使用name=value的形式。

    3.3 使用@Documented   

    @Documented用于指定该元Annotation修饰的Annotation类将被javadoc工具提成文档,如果定义Annotation类时使用了@Documented修饰,则所有使用该Annotation修饰的程序元素的API文档将会包含该Annotation说明。
    下面代码定义了一个Testable Annotation,使用@Documented来修饰@Testable Annotation定义,所以该Annotation将被javadoc工具所提取。

     1 import java.lang.annotation.*;
     2 
     3 @Retention(RetentionPolicy.RUNTIME)    
     4 @Target(ElementType.METHOD)
     5 //定义Testable Annotation将被javadoc工具提取
     6 //@Documented 
     7 public @interface Testable{
     8 }
     9 所有使用@Testable Annotation的地方都会被javadoc工具提取到API文档中。
    10 
    11 public class MyTest{
    12     // 使用@Test修饰info方法
    13     @Testable
    14     public void info(){
    15         System.out.println("info方法...");
    16     }
    17 }
    View Code

    3.4 使用@Inherited        

      @Inherited元Annotation指定被它修饰的Annotation将具有继承性:如果某个类使用了A Annotation(定义该Annotation使用了@Inherited修饰)修饰,则其子类将自动具有A注释
      下面使用@Inherited元数据注释定义一个Inheritable Annotation,该Annotation将具有继承性。

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface Inheritable{
    
    }
    View Code

      上面代码表明@Inheritable Annotation具有继承性,如果某个类使用了该Annotation修饰,则该类的子类将自动具有@Inheritable Annotation
    下面程序定义一个Base基类,该基类使用了@Inheritable修饰,则Base类的子类将自动具有@Inheritable Annotation

    //使用@Inheritable修饰的Base类
    class Base{
    
    }
    //TestInheritable类只是继承了Base类
    //并未直接使用@Inheritable Annotation修饰
    public class TestInheritable extends Base{
        public static void main(String[] args){
            //打印TestInheritable类是否具有Inheritable Annotation
            System.out.println(TestInheritable.class.isAnnotationPresent(Inheritable.class))
        }
    }
    运行结果是:true,表面TestInheritable具有@Inheritable Annotation
    View Code
  • 相关阅读:
    How can i use iptables on centos 7?
    Running Jenkins behind Nginx
    GPG入门教程
    HTML5 canvas标签-4 灰度直方图的实现
    [转载]手把手用C++解密Chrome80版本数据库
    Delphi
    7-zip Delphi API
    cef对本地web资源打包加密
    CEF3资源重定向、读取加密资源、读取zip资源
    axios设置withCredentials导致“跨域”的解决方案
  • 原文地址:https://www.cnblogs.com/people/p/3078962.html
Copyright © 2011-2022 走看看