注解概述

什么是注解

什么是注解(Annotation)?注解是放在Java源码的类、方法、字段、参数前的一种用作标注的“元数据”。

注解的作用

从JVM的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。
Java的注解可以分为三类:
第一类是由编译器使用的注解,例如:

  • @Override:让编译器检查该方法是否正确地实现了覆写;
  • @SuppressWarnings:告诉编译器忽略此处代码产生的警告。

这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。

第二类是由工具处理.class文件使用的注解,比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。

第三类是在程序运行期能够读取的注解,它们在加载后一直存在于JVM中,这也是最常用的注解。例如,一个配置了@PostConstruct的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)。

定义注解

Java语言使用@interface语法来定义注解(Annotation),它的格式如下:

  1. public @interface Report {
  2. int type() default 0;
  3. String level() default "info";
  4. String value() default "";
  5. }

注解的参数类似无参数方法,可以用default设定一个默认值(强烈推荐)。最常用的参数应当命名为value。

注解的参数

定义一个注解时,还可以定义配置参数。配置参数可以包括:

  • 所有基本类型;
  • String;
  • 枚举类型;
  • 基本类型、String、Class以及枚举的数组。

因为配置参数必须是常量,所以,上述限制保证了注解在定义时就已经确定了每个参数的值。
注解的配置参数可以有默认值,缺少某个配置参数时将使用默认值。
此外,大部分注解会有一个名为value的配置参数,对此参数赋值,可以只写常量,相当于省略了value参数。如果只写注解,相当于全部使用默认值。

举个栗子,对以下代码:

  1. public class Hello {
  2. @Check(min=0, max=100, value=55)
  3. public int n;
  4. @Check(value=99)
  5. public int p;
  6. @Check(99) // @Check(value=99)
  7. public int x;
  8. @Check
  9. public int y;
  10. }

@Check就是一个注解。第一个@Check(min=0, max=100, value=55)明确定义了三个参数,第二个@Check(value=99)只定义了一个value参数,它实际上和@Check(99)是完全一样的。最后一个@Check表示所有参数都使用默认值。

元注解

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

@Target

最常用的元注解是@Target。使用@Target可以定义Annotation能够被应用于源码的哪些位置:

  • 类或接口:ElementType.TYPE
  • 字段:ElementType.FIELD
  • 方法:ElementType.METHOD
  • 构造方法:ElementType.CONSTRUCTOR
  • 方法参数:ElementType.PARAMETER

例如,定义注解@Report可用在方法上,我们必须添加一个@Target(ElementType.METHOD):

  1. @Target(ElementType.METHOD)
  2. public @interface Report {
  3. int type() default 0;
  4. String level() default "info";
  5. String value() default "";
  6. }

定义注解@Report可用在方法或字段上,可以把@Target注解参数变为数组{ ElementType.METHOD, ElementType.FIELD }

  1. @Target({
  2. ElementType.METHOD,
  3. ElementType.FIELD
  4. })
  5. public @interface Report {
  6. ...
  7. }

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

@Retention

另一个重要的元注解@Retention定义了Annotation的生命周期:

  • 仅编译期:RetentionPolicy.SOURCE
  • 仅class文件:RetentionPolicy.CLASS
  • 运行期:RetentionPolicy.RUNTIME

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

  1. @Retention(RetentionPolicy.RUNTIME)
  2. public @interface Report {
  3. int type() default 0;
  4. String level() default "info";
  5. String value() default "";
  6. }

@Repeatable

使用@Repeatable这个元注解可以定义Annotation是否可重复。这个注解应用不是特别广泛。

  1. @Repeatable(Reports.class)
  2. @Target(ElementType.TYPE)
  3. public @interface Report {
  4. int type() default 0;
  5. String level() default "info";
  6. String value() default "";
  7. }
  8. @Target(ElementType.TYPE)
  9. public @interface Reports {
  10. Report[] value();
  11. }

经过@Repeatable修饰后,在某个类型声明处,就可以添加多个@Report注解:

  1. @Report(type=1, level="debug")
  2. @Report(type=2, level="warning")
  3. public class Hello {
  4. }

@Inherited

使用@Inherited定义子类是否可继承父类定义的Annotation@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效:

  1. @Inherited
  2. @Target(ElementType.TYPE)
  3. public @interface Report {
  4. int type() default 0;
  5. String level() default "info";
  6. String value() default "";
  7. }

在使用的时候,如果一个类用到了@Report

  1. @Report(type=1)
  2. public class Person {
  3. }

则它的子类默认也定义了该注解:

  1. public class Student extends Person {
  2. }

如何定义Annotation

我们总结一下定义Annotation的步骤:
第一步,用@interface定义注解:

  1. public @interface Report { }

第二步,添加参数、默认值:

  1. public @interface Report {
  2. int type() default 0;
  3. String level() default "info";
  4. String value() default "";
  5. }

把最常用的参数定义为value(),推荐所有参数都尽量设置默认值。
第三步,用元注解配置注解:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface Report {
  4. int type() default 0;
  5. String level() default "info";
  6. String value() default "";
  7. }

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

处理注解

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

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

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

  1. 判断某个注解是否存在于ClassFieldMethodConstructor
  • Class.isAnnotationPresent(Class)
  • Field.isAnnotationPresent(Class)
  • Method.isAnnotationPresent(Class)
  • Constructor.isAnnotationPresent(Class)
  1. 使用反射API读取Annotation
  • Class.getAnnotation(Class)
  • Field.getAnnotation(Class)
  • Method.getAnnotation(Class)
  • Constructor.getAnnotation(Class)

读取Class的注解:

  1. //Class cls = Person.class;
  2. //if (cls.isAnnotationPresent(Report.class)) {
  3. // Report report = cls.getAnnotation(Report.class);
  4. // ...
  5. //}
  6. Class cls = Person.class;
  7. Report report = cls.getAnnotation(Report.class);
  8. if (report != null) {
  9. ...
  10. }

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

  1. // 获取Method实例:
  2. Method m = ...
  3. // 获取所有参数的Annotation:
  4. Annotation[][] annos = m.getParameterAnnotations();
  5. // 第一个参数(索引为0)的所有Annotation:
  6. Annotation[] annosOfName = annos[0];
  7. for (Annotation anno : annosOfName) {
  8. if (anno instanceof Range) { // @Range注解
  9. Range r = (Range) anno;
  10. }
  11. if (anno instanceof NotNull) { // @NotNull注解
  12. NotNull n = (NotNull) anno;
  13. }
  14. }

测试

定义注解:

  1. package com.hanliukui.annotationtest;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. /**
  7. * @Author hanliukui
  8. * @Date 2022/5/5 17:04
  9. * @Description xxx
  10. */
  11. @Target(ElementType.TYPE)
  12. @Retention(RetentionPolicy.RUNTIME)
  13. public @interface ClassAnnotation {
  14. String value() default "";
  15. }
  1. package com.hanliukui.annotationtest;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. /**
  7. * @Author hanliukui
  8. * @Date 2022/5/5 16:43
  9. * @Description xxx
  10. */
  11. @Target(ElementType.FIELD)
  12. @Retention(RetentionPolicy.RUNTIME)
  13. public @interface FieldAnnotation {
  14. int min() default 1;
  15. int max() default 999;
  16. }
  1. package com.hanliukui.annotationtest;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. /**
  7. * @Author hanliukui
  8. * @Date 2022/5/5 17:04
  9. * @Description xxx
  10. */
  11. @Target(ElementType.PARAMETER)
  12. @Retention(RetentionPolicy.RUNTIME)
  13. public @interface ParamAnnotationNotNull {
  14. boolean value() default false;
  15. }

在类上使用注解:

  1. package com.hanliukui.annotationtest;
  2. /**
  3. * @Author hanliukui
  4. * @Date 2022/5/5 16:58
  5. * @Description xxx
  6. */
  7. @ClassAnnotation("aaa")
  8. public class AppInfo {
  9. @FieldAnnotation(min = 3,max = 888)
  10. private Integer price;
  11. AppInfo(Integer price){
  12. this.price = price;
  13. }
  14. public void sale(@ParamAnnotationNotNull(value = true) String username){
  15. System.out.println(username+"1111");
  16. }
  17. }

测试:

  1. package com.hanliukui.annotationtest;
  2. import java.lang.annotation.Annotation;
  3. import java.lang.reflect.Field;
  4. import java.lang.reflect.Method;
  5. /**
  6. * @Author hanliukui
  7. * @Date 2022/5/5 16:43
  8. * @Description xxx
  9. */
  10. public class AnnotationTest {
  11. public static void main(String[] args) {
  12. AppInfo appInfo = new AppInfo(20);
  13. Class<? extends AppInfo> aClass = appInfo.getClass();
  14. Field[] fields = aClass.getDeclaredFields();
  15. for (Field f:fields){
  16. FieldAnnotation fieldAnnotation = f.getAnnotation(FieldAnnotation.class);
  17. if (fieldAnnotation !=null){
  18. int max = fieldAnnotation.max();
  19. int min = fieldAnnotation.min();
  20. System.out.println("max="+max);
  21. System.out.println("min="+min);
  22. }
  23. }
  24. ClassAnnotation classAnnotation = aClass.getAnnotation(ClassAnnotation.class);
  25. System.out.println(classAnnotation.value());
  26. Method[] methods = aClass.getDeclaredMethods();
  27. for (Method method:methods){
  28. Annotation[][] parameterAnnotations = method.getParameterAnnotations();
  29. for (Annotation[] annotations:parameterAnnotations){
  30. for (Annotation annotation:annotations){
  31. if (annotation instanceof ParamAnnotationNotNull){
  32. ParamAnnotationNotNull annotationNotNull = (ParamAnnotationNotNull) annotation;
  33. System.out.println(annotationNotNull.value());
  34. }
  35. }
  36. }
  37. }
  38. }
  39. }
  40. // max=888
  41. // min=3
  42. // aaa
  43. // true