• 处理注解


    Java的注解本身对代码逻辑没有任何影响。根据@Retention的配置:

    • SOURCE类型的注解在编译期就被丢掉了;
    • CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM;
    • RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取。

    如何使用注解完全由工具决定。SOURCE类型的注解主要由编译器使用,因此我们一般只使用,不编写。CLASS类型的注解主要由底层工具库使用,涉及到class的加载,一般我们很少用到。只有RUNTIME类型的注解不但要使用,还经常需要编写。

    因此,我们只讨论如何读取RUNTIME类型的注解。

    因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此,读取注解,需要使用反射API。

    Java提供的使用反射API读取Annotation的方法包括:

    判断某个注解是否存在于ClassFieldMethodConstructor

    • Class.isAnnotationPresent(Class)
    • Field.isAnnotationPresent(Class)
    • Method.isAnnotationPresent(Class)
    • Constructor.isAnnotationPresent(Class)
    // 判断@Report是否存在于Person类:
    Person.class.isAnnotationPresent(Report.class);

    使用反射API读取Annotation:

    • Class.getAnnotation(Class)
    • Field.getAnnotation(Class)
    • Method.getAnnotation(Class)
    • Constructor.getAnnotation(Class)
    // 获取Person定义的@Report注解:
    Report report = Person.class.getAnnotation(Report.class);
    //获取@Report注解的参数
    int type = report.type(); String level = report.level();

    使用反射API读取Annotation有两种方法。方法一是先判断Annotation是否存在,如果存在,就直接读取:

    Class cls = Person.class;
    if (cls.isAnnotationPresent(Report.class)) {
        Report report = cls.getAnnotation(Report.class);
        ...
    }

    第二种方法是直接读取Annotation,如果Annotation不存在,将返回null

    Class cls = Person.class;
    Report report = cls.getAnnotation(Report.class);
    if (report != null) {
       ...
    }

    读取方法、字段和构造方法的Annotation和Class类似。但要读取方法参数的Annotation就比较麻烦一点,因为方法参数本身可以看成一个数组,而每个参数又可以定义多个注解,所以,一次获取方法参数的所有注解就必须用一个二维数组来表示。例如,对于以下方法定义的注解:

    public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) {
    }

    要读取方法参数的注解,我们先用反射获取Method实例,然后读取方法参数的所有注解:

    // 获取Method实例:
    Method m = ...
    // 获取所有参数的Annotation:
    Annotation[][] annos = m.getParameterAnnotations();
    // 第一个参数(索引为0)的所有Annotation:
    Annotation[] annosOfName = annos[0];
    for (Annotation anno : annosOfName) {
        if (anno instanceof Range) { // @Range注解
            Range r = (Range) anno;
        }
        if (anno instanceof NotNull) { // @NotNull注解
            NotNull n = (NotNull) anno;
        }
    }

    使用注解

    注解如何使用,完全由程序自己决定。例如,JUnit是一个测试框架,它会自动运行所有标记为@Test的方法。

    我们来看一个@Range注解,我们希望用它来定义一个String字段的规则:字段长度满足@Range的参数定义:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Range {
        int min() default 0;
        int max() default 255;
    }

    在某个JavaBean中,我们可以使用该注解:

    public class Person {
        @Range(min=1, max=20)
        private String name;
    
        @Range(max=10)
        private String city;
    }

    但是,定义了注解,本身对程序逻辑没有任何影响。我们必须自己编写代码来使用注解。这里,我们编写一个Person实例的检查方法,它可以检查Person实例的String字段长度是否满足@Range的定义:

     1 import java.lang.reflect.*;
     2 import java.lang.annotation.*;
     3 public class Demo12{        
     4     public static void main(String[] args)throws Exception{  
     5         Person person = new Person();
     6         person.setName("汪天");
     7         person.setCity("武汉");
     8         //检测person对象字段长度是否符合要求
     9         
    10         /* try{
    11             check(person);
    12             System.out.println(person.getName() + " 输入成功!");
    13             System.out.println(person.getCity() + " 输入成功!");
    14         }catch(Exception e){
    15             System.out.println("字段输入错误!");
    16             e.printStackTrace();
    17         }
    18         System.out.println("使用try-catch处理异常后,下面的程序会继续执行");
    19         */
    20         
    21         check(person);
    22         System.out.println(person.getName() + " 输入成功!");
    23         System.out.println(person.getCity() + " 输入成功!");
    24         
    25         System.out.println("不使用try-catch处理异常,在主方法main声明处throws Exception抛出异常,则程序编译到出现异常的语句那里就报错,程序终止,不会执行下面的程序");
    26     }
    27     
    28     /*
    29     检测person对象字段长度是否符合要求
    30     */
    31     static void check(Person person) throws IllegalArgumentException,ReflectiveOperationException{
    32         //获取所有的Field,其中因为有private私有字段,所以必须用getDeclaredFields()去获取才行
    33         Field[] fields = person.getClass().getDeclaredFields();
    34         //遍历所有的Field
    35         for(Field field:fields){
    36             Range range = field.getAnnotation(Range.class);
    37             if(range != null){
    38                 //private私有字段,开启访问权限
    39                 field.setAccessible(true);
    40                 //获取field的值
    41                 Object value = field.get(person);
    42                 // 如果值是String:
    43                 if(value instanceof String){
    44                     String s = (String)value;
    45                     System.out.println("输入"+ field.getName() + ":" + s);
    46                     // 判断值是否满足@Range的min/max:
    47                     if(s.length() < range.min() || s.length() > range.max()){
    48                     throw new IllegalArgumentException("Invalid field:" + field.get(person));
    49                     }
    50                 }
    51             }
    52         }
    53     }
    54 }
    55 
    56 @Retention(RetentionPolicy.RUNTIME)//定义程序运行时注解将被JVM加载
    57 @Target(ElementType.FIELD) //定义注解作用在字段上
    58 @interface Range{
    59     int min() default 0;
    60     int max() default 255;
    61 }
    62 
    63 class Person{
    64     @Range(min=2,max=4)
    65     private String name;
    66     
    67     @Range(min=2,max=4)
    68     private String city;
    69     
    70     public void setName(String name){
    71         this.name = name;
    72     }
    73     
    74     public void setCity(String city){
    75         this.city = city;
    76     }
    77     
    78     public String getName(){
    79         return name;
    80     }
    81     
    82     public String getCity(){
    83         return city;
    84     }
    85 }

    这样一来,我们通过@Range注解,配合check()方法,就可以完成Person实例的检查。注意检查逻辑完全是我们自己编写的,JVM不会自动给注解添加任何额外的逻辑。

    小结

    可以在运行期通过反射读取RUNTIME类型的注解,注意千万不要漏写@Retention(RetentionPolicy.RUNTIME),否则运行期无法读取到该注解。

    可以通过程序处理注解来实现相应的功能:

    • 对JavaBean的属性值按规则进行检查;
    • JUnit会自动运行@Test标记的测试方法。
  • 相关阅读:
    5.18英语
    5.18
    5.17
    单源点最短路模板
    5.16
    mock.js进行接口mock
    docker-compose安装和使用
    docker常用命令
    docker安装和使用(win10家庭版)
    ES6基础(2)-const
  • 原文地址:https://www.cnblogs.com/zui-ai-java/p/14246878.html
Copyright © 2020-2023  润新知