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

  1. 由编译器使用的注解,例如
    1. @Override:让编译器检查该方法是否正确的实现了覆写
    2. @SuppressWarnings:告诉编译器忽略此处代码产生的警告
  2. 由工具处理 .class 文件使用的注解,比如有些工具在加载 class 的时候,对 class 做动态修改实现一些特殊的功能。使用频率不高
  3. 在程序运行期能够读取的注解,它们在加载后一直存在于 JVM 中。这一种是最常用到的注解

    定义注解

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

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

    注解的参数类似无参数方法,可以用 default 设定一个默认值(强烈推荐)。最常用的参数应当命名为 value。定义注解时,如果参数名称是 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 表示所有参数都使用默认值。

    元注解

    用于修饰其他注解的注解被称为元注解。Java 标准库已经定义了很多元注解,通常不会自己编写元注解

    @Target

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

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

    定义注解可用在多处位置是,可以把 @Target 注解参数变为数组。例如:{ ElementType.METHOD,ElementType.FIELD } 表示注解可用在方法或字段上

@Retention

@Retention 定义 Annotation 的生命周期

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

    如果 @Retention 不存在,则该 Annotation 默认为 CLASS。实际开发过程中,定义的 Annotation 生命周期都是 RUNTIME,所以务必要加上 @Retention(RetentionPolicy.RUNTIME) 这个元注解

@Repeatable

@Repeatable 定义 Annotation 是否可重复声明。这个注解用的比较少

@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. }

处理注解

Java 的注解本身对代码逻辑没有影响。其生命周期根据 @Retention 的配置决定。因为注解定义后也是一种 class,所有的注解都继承自 java.lang.annotation.Annotation,所以读取注解需要用到反射 API
Java 反射 API 提供的用于读取 Annotation 的方法包括:

  • 判断某个注解是否存在于 ClassFieldMethodConstructor
    • Class.isAnnotationPresent(Class)
    • Field.isAnnotationPresent(Class)
    • Method.isAnnotationPresent(Class)
    • Constructor.isAnnotationPresent(Class)
  • 读取注解 Annotation,如果 Annotation 不存在,将返回 null
    • Class.getAnnotation(Class)
    • Field.getAnnotation(Class)
    • Method.getAnnotation(Class)
    • Constructor.getAnnotation(Class)

      定义参数校验注解工具类

  1. 定义注解 @Range,针对数字型校验最大和最小值、字符串型校验最大和最小长度 ```java import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;

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

  1. 2. 定义用于接收校验结果的输出模型类 `ValidationResult` `ValidationError`
  2. 1. `isValid` 属性表示 `JavaBean` 是否通过校验
  3. 1. `errors` 属性存储校验失败的字段和失败原因
  4. ```java
  5. import lombok.Getter;
  6. import lombok.Setter;
  7. import lombok.ToString;
  8. import java.util.List;
  9. @ToString
  10. public class ValidationResult {
  11. @Getter
  12. @Setter
  13. private boolean isValid;
  14. @Getter
  15. @Setter
  16. private List<ValidationError> errors;
  17. }
  18. import lombok.Getter;
  19. import lombok.Setter;
  20. import lombok.ToString;
  21. import java.lang.reflect.Field;
  22. @ToString
  23. public class ValidationError {
  24. @Getter
  25. @Setter
  26. private Field field;
  27. @Getter
  28. @Setter
  29. private String message;
  30. }
  1. 定义字段校验工具类 FieldValidation,工作流程大致分为

    1. 读取 JavaBean 实例类型
    2. 读取 JavaBean 中字段
    3. 遍历字段是否声明 @Range 注解,如果定义则根据规则校验字段内容是否合法

      1. public abstract class FieldValidation {
      2. /**
      3. * 根据特性校验字段值
      4. *
      5. * @param instance 对象示例
      6. * @return boolean
      7. */
      8. public static ValidationResult check(Object instance) throws IllegalAccessException {
      9. final ValidationResult result = new ValidationResult();
      10. final ArrayList<ValidationError> errors = CollUtil.newArrayList();
      11. final Class<?> instanceClass = instance.getClass();
      12. final Field[] declaredFields = instanceClass.getDeclaredFields();
      13. if (ArrayUtil.isEmpty(declaredFields)) {
      14. result.setValid(true);
      15. return result;
      16. }
      17. for (Field field : declaredFields) {
      18. if (!field.canAccess(instance)) {
      19. field.setAccessible(true);
      20. }
      21. if (!field.isAnnotationPresent(Range.class)) {
      22. continue;
      23. }
      24. final Range range = field.getAnnotation(Range.class);
      25. final int min = range.min();
      26. final int max = range.max();
      27. if (field.getType() == int.class) {
      28. final var value = (int) field.get(instance);
      29. if (value < min) {
      30. final ValidationError error = new ValidationError();
      31. error.setField(field);
      32. error.setMessage(String.format("value = %s,can't less than %s", value, min));
      33. errors.add(error);
      34. }
      35. if (value > max) {
      36. final ValidationError error = new ValidationError();
      37. error.setField(field);
      38. error.setMessage(String.format("value = %s,can't greater than %s", value, max));
      39. errors.add(error);
      40. }
      41. continue;
      42. }
      43. if (field.getType() == String.class) {
      44. final var value = (String) field.get(instance);
      45. if (StrUtil.isEmpty(value)) {
      46. final ValidationError error = new ValidationError();
      47. error.setField(field);
      48. error.setMessage("value can't be null");
      49. errors.add(error);
      50. continue;
      51. }
      52. final int length = value.length();
      53. if (length < min) {
      54. final ValidationError error = new ValidationError();
      55. error.setField(field);
      56. error.setMessage(String.format("length can't greater than %s,but %s", max, length));
      57. errors.add(error);
      58. }
      59. if (length > max) {
      60. final ValidationError error = new ValidationError();
      61. error.setField(field);
      62. error.setMessage(String.format("length can't greater than %s,but %s", max, length));
      63. errors.add(error);
      64. }
      65. }
      66. }
      67. result.setErrors(errors);
      68. result.setValid(errors.isEmpty());
      69. return result;
      70. }
      71. }
  2. 使用 @Range 示例 DemoBean ```java import lombok.Setter;

public class DemoBean {

  1. @Setter
  2. @Range(min = 1, max = 200)
  3. private int age;
  4. @Setter()
  5. @Range(min = 1, max = 3)
  6. private String name;

}

  1. 5. 调用示例
  2. ```java
  3. @Slf4j
  4. public class Main {
  5. public static void main(String[] args) {
  6. final DemoBean demoBean = new DemoBean();
  7. demoBean.setAge(8989);
  8. demoBean.setName("xcxcxcxc");
  9. ValidationResult result = new ValidationResult();
  10. try {
  11. result = FieldValidation.check(demoBean);
  12. } catch (IllegalAccessException e) {
  13. e.printStackTrace();
  14. }
  15. log.info("Validation return {}",result.toString());
  16. }
  17. }
  18. // 打印结果
  19. 22:17:29.127 [main] INFO com.dev.Main - Validation return ValidationResult(isValid=false, errors=[ValidationError(field=private int com.dev.annotation.DemoBean.age, message=value = 8989,can't greater than 200), ValidationError(field=private java.lang.String com.dev.annotation.DemoBean.name, message=length can't greater than 3,but 8)])