您的位置:首页 > 编程语言 > Java开发

Java学习_注解

2021-01-10 20:54 1021 查看
  • 使用注解 注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”。 
    1 // this is a component:
    2 @Resource("hello")
    3 public class Hello {
    4     @Inject
    5     int n;
    6
    7     @PostConstruct
    8     public void hello(@Param String name) {
    9         System.out.println(name);
    10     }
    11
    12     @Override
    13     public String toString() {
    14         return "Hello";
    15     }
    16 }

    注释会被编译器直接忽略,注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”。

  • 从JVM的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。

  • Java的注解可以分为三类:

    第一类是由编译器使用的注解,这类注解不会被编译进入

    .class
    文件,它们在编译后就被编译器扔掉了。

    @Override
    :让编译器检查该方法是否正确地实现了覆写;
  • @SuppressWarnings
    :告诉编译器忽略此处代码产生的警告。
  • 第二类是由工具处理
    .class
    文件使用的注解,比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入
    .class
    文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。
  • 第三类是在程序运行期能够读取的注解,它们在加载后一直存在于JVM中,这也是最常用的注解。例如,一个配置了
    @PostConstruct
    的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)。
  • 定义一个注解时,还可以定义配置参数。配置参数可以包括:

      所有基本类型;
    • String;
    • 枚举类型;
    • 基本类型、String、Class以及枚举的数组。
  • 因为配置参数必须是常量,所以,上述限制保证了注解在定义时就已经确定了每个参数的值。
  • 注解的配置参数可以有默认值,缺少某个配置参数时将使用默认值。此外,大部分注解会有一个名为
    value
    的配置参数,对此参数赋值,可以只写常量,相当于省略了value参数。如果只写注解,相当于全部使用默认值。
    public class Hello {
    @Check(min=0, max=100, value=55)
    public int n;
    
    @Check(value=99)
    public int p;
    
    @Check(99) // @Check(value=99)
    public int x;
    
    @Check
    public int y;
    }
  • 定义注解
      Java语言使用
      @interface
      语法来定义注解(
      Annotation
      ),它的格式为:
      public @interface Report {
      int type() default 0;
      String level() default "info";
      String value() default "";
      }

      注解的参数类似无参数方法,可以用

      default
      设定一个默认值(强烈推荐)。最常用的参数应当命名为
      value

    • 有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解。

    • 最常用的元注解是

      @Target
      。使用
      @Target
      可以定义
      Annotation
      能够被应用于源码的哪些位置。

      类或接口:
      ElementType.TYPE
    • 字段:
      ElementType.FIELD
    • 方法:
      ElementType.METHOD
    • 构造方法:
      ElementType.CONSTRUCTOR
    • 方法参数:
      ElementType.PARAMETER
  • 例如,定义注解
    @Report
    可用在方法上,我们必须添加一个
    @Target(ElementType.METHOD)
    。定义注解
    @Report
    可用在方法或字段上,可以把
    @Target
    注解参数变为数组
    { ElementType.METHOD, ElementType.FIELD }
    @Target(ElementType.METHOD)
    public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
    }
    @Target({
    ElementType.METHOD,
    ElementType.FIELD
    })
    public @interface Report {
    ...
    }

    实际上

    @Target
    定义的
    value
    ElementType[]
    数组,只有一个元素时,可以省略数组的写法。

  • 另一个重要的元注解
    @Retention
    定义了
    Annotation
    的生命周期。
      仅编译期:
      RetentionPolicy.SOURCE
    • 仅class文件:
      RetentionPolicy.CLASS
    • 运行期:
      RetentionPolicy.RUNTIME
  •                     如果

    @Retention
    不存在,则该
    Annotation
    默认为
    CLASS
    。因为通常我们自定义的
    Annotation
    都是
    RUNTIME
    ,所以,务必要加上
    @Retention(RetentionPolicy.RUNTIME)
    这个元注解。

    • [li]
      @Repeatable
      这个元注解可以定义
      Annotation
      是否可重复。  
      @Repeatable(Reports.class)
      @Target(ElementType.TYPE)
      public @interface Report {
      int type() default 0;
      String level() default "info";
      String value() default "";
      }
      
      @Target(ElementType.TYPE)
      public @interface Reports {
      Report[] value();
      }
      经过@Repeatable修饰后,在某个类型声明处,就可以添加多个@Report注解:
      
      @Report(type=1, level="debug")
      @Report(type=2, level="warning")
      public class Hello {
      }
    • 使用
      @Inherited
      定义子类是否可继承父类定义的
      Annotation
      @Inherited
      仅针对
      @Target(ElementType.TYPE)
      类型的
      annotation
      有效,并且仅针对
      class
      的继承,对
      interface
      的继承无效。
      @Inherited
      @Target(ElementType.TYPE)
      public @interface Report {
      int type() default 0;
      String level() default "info";
      String value() default "";
      }
      在使用的时候,如果一个类用到了@Report:
      
      @Report(type=1)
      public class Person {
      }
      则它的子类默认也定义了该注解:
      
      public class Student extends Person {
      }
    • 如何定义Annotation

      1 //第一步,用@interface定义注解:
      2
      3 public @interface Report {
      4 }
      5
      6 //第二步,添加参数、默认值:
      7
      8 public @interface Report {
      9     int type() default 0;
      10     String level() default "info";
      11     String value() default "";
      12 }
      13 //把最常用的参数定义为value(),推荐所有参数都尽量设置默认值。
      14
      15 //第三步,用元注解配置注解:
      16
      17 @Target(ElementType.TYPE)
      18 @Retention(RetentionPolicy.RUNTIME)
      19 public @interface Report {
      20     int type() default 0;
      21     String level() default "info";
      22     String value() default "";
      23 }

      其中,必须设置

      @Target
      @Retention
      @Retention
      一般设置为
      RUNTIME
      ,因为我们自定义的注解通常要求在运行期读取。一般情况下,不必写
      @Inherited
      @Repeatable

    [/li]
    • 处理注解

      SOURCE
      类型的注解主要由编译器使用,因此我们一般只使用,不编写。
      CLASS
      类型的注解主要由底层工具库使用,涉及到class的加载,一般我们很少用到。只有
      RUNTIME
      类型的注解不但要使用,还经常需要编写。
    • Java提供的使用反射API读取

      Annotation
      的方法包括:

      判断某个注解是否存在于

      Class
      Field
      Method
      Constructor

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

           使用反射API读取Annotation:

    • [li]
      Class.getAnnotation(Class)
    • Field.getAnnotation(Class)
    • Method.getAnnotation(Class)
    • Constructor.getAnnotation(Class)
      // 获取Person定义的@Report注解:
      Report report = Person.class.getAnnotation(Report.class);
      int type = report.type();
      String level = report.level();
    [/li]
  • 使用反射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)
      public String name;
      
      @Range(max=10)
      public String city;
      }

      但是,定义了注解,本身对程序逻辑没有任何影响。我们必须自己编写代码来使用注解。这里,我们编写一个

      Person
      实例的检查方法,它可以检查
      Person
      实例的
      String
      字段长度是否满足
      @Range
      的定义:

      void check(Person person) throws IllegalArgumentException, ReflectiveOperationException {
      // 遍历所有Field:
      for (Field field : person.getClass().getFields()) {
      // 获取Field定义的@Range:
      Range range = field.getAnnotation(Range.class);
      // 如果@Range存在:
      if (range != null) {
      // 获取Field的值:
      Object value = field.get(person);
      // 如果值是String:
      if (value instanceof String) {
      String s = (String) value;
      // 判断值是否满足@Range的min/max:
      if (s.length() < range.min() || s.length() > range.max()) {
      throw new IllegalArgumentException("Invalid field: " + field.getName());
      }
      }
      }
      }
      }

      这样一来,我们通过

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

  • 内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: