Skip to content

自定义注解

先说一下元注解

  1. Target:
    • Target 注解的作用是:描述注解的使用范围(即:被修饰的注解可以用在什么地方)
java
public enum ElementType {

    TYPE, // 类、接口、枚举类

    FIELD, // 成员变量(包括:枚举常量)

    METHOD, // 成员方法

    PARAMETER, // 方法参数

    CONSTRUCTOR, // 构造方法

    LOCAL_VARIABLE, // 局部变量

    ANNOTATION_TYPE, // 注解类

    PACKAGE, // 可用于修饰:包

    TYPE_PARAMETER, // 类型参数,JDK 1.8 新增

    TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
  1. @Retention 注解
    • Reteniton 注解的作用是:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时)
    • 一共有 3 种: SOURCE 源文件保留,CLASS 编译期保留(默认值),RUNTIME 运行时保留
  2. @Decumented 注解
    • Documented 注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息
  3. @Inherited 注解
    • Inherited 注解的作用是:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited 修饰的注解,则其子类将自动具有该注解)。

自定义注解校验

比如资定义一个正则表达式校验注解:

java
@Constraint(validatedBy = RegexpValidator.class)
@Target({ElementType.FIELD})
@Retention(RUNTIME)
@Documented
public @interface ValidRegexp {
    String type();
    String message() default "invalid";
    Class<?>[] groups() default {};
    Class<?>[] payload() default {};
}

ValidRegexp就是自定义的注解,@Retention(RUNTIME)表示保留到运行时,RegexpValidator是对应自定义的校验器,需要在校验器里面写校验逻辑 @Target({ElementType.FIELD})表示用在成员变量上

java
public class RegexpValidator implements ConstraintValidator<ValidRegexp, String> {
    @Autowired
    ValidatorConfig config;
    private String regexp;
    @Override
    public void initialize(ValidRegexp constraintAnno) {
        try {
            var type = constraintAnno.type();
            var field = config.getClass().getField(type);
            var b = (ValidatorConfig.BaseValidator) field.get(config);
            regexp = b.getRegexp();
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        return s != null && s.matches(regexp);
    }
}

RegexpValidator需要实现ConstraintValidator<ValidRegexp, String>接口,重写 initialize 和 isValid 方法. 看一下 matches 方法的定义:

java
public boolean matches(String regex) {
    return Pattern.matches(regex, this);
}

initialize 方法会在验证器实例化后被调用。这里首先从 constraintAnno 获取类型名,然后从 config 对象中反射获取对应的字段,并从中提取出正则表达式。如果反射操作失败,会抛出一个运行时异常.

这个校验器的还有一个就是ValidatorConfig config是什么? 这是在另外一个地方定义的:

java
@Data
@Configuration
@ConfigurationProperties(prefix = "app.validator")
public class ValidatorConfig {
    @Data
    public static class BaseValidator {
        String regexp;
        String message;
    }

    public Username username;
    public Password password;
    public Tel tel;

    public static class Password extends BaseValidator {
    }

    public static class Username extends BaseValidator {
    }

    public static class Tel extends BaseValidator {
    }
}

这个是校验器配置,内部定义了一个 BaseValidator,然后有三个派生类继承了 BaseValidator。 BaseValidator 有两个字段,分别用于存储正则表达式和验证失败时的消息。

(prefix = "app.validator") 是表示在 application.yml 这个文件中的

app:
  jwt:
    key: dut233
    expired-time: 48h
  validator:
    username:
      message: 用户名必须是6-16位的字母或数字
      regexp: ^[0-9A-Za-z]{6,16}$
    password:
      message: 密码必须包含数字和字母,且长度在6-16位之间
      regexp: ^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,16}$
    tel:
      message:
      regexp: ^(?:\+?86)?1(?:3\d{3}|5[^4\D]\d{2}|8\d{3}|7(?:[235-8]\d{2}|4(?:0\d|1[0-2]|9\d))|9[0-35-9]\d{2}|66\d{2})\d{6}$

这些配置会被映射到 ValidatorConfig 类的相应属性中,这样就可以在你的应用中使用这些配置来进行验证逻辑的编写了。

于是 var type = constraintAnno.type()就是ValidRegexp中的String typevar field = config.getClass().getField(type)就是ValidatorConfig config中的使用反射获取  ValidatorConfig  类中名为  type  的字段,例如  username、password  或  tel field.get(config)  获取  config  对象中  field  字段的值 regexp = b.getRegexp();获取到转换的这个类型之后获取String regexp的值

举个例子更好理解: type 传进来的是 username,然后 field 是找到 config 中的叫 username 的一个成员变量,找到之后再用 field.get(config)获取对应 username 的值是什么,比如就是 由于 Username 是 username 的类型,所以对应的值就是 Username 结构体中对应的值,在这里就是配置文件中的 username: message: 用户名必须是6-16位的字母或数字 regexp: ^[0-9A-Za-z]{6,16}$ 由于 Username 类继承了一个 Base,所以再把它转换成 Base 类型,方便后面获取其中的 regexp。

为了进一步理解:

java
public class Main
{
    private static class Apple{
        public String name;

        Apple(String n){
            name = n;
        }
    };
    public static void main(String[] args) {
        Apple apple=new Apple("an apple");
        var type = "name";
        try{
        var field= apple.getClass().getField(type);
        System.out.println(field);//name
        System.out.println(field.getType());//String
        System.out.println(field.get(apple));//apple的name字段的值
        var b=(String)(field.get(apple));
        System.out.println(b.length());
        }catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

输出:

public java.lang.String Main$Apple.name
class java.lang.String
an apple
8

使用:

java
@ValidRegexp(type = "tel")
private String tel;

@ValidRegexp(type = "username", message = "invalid username")
private String name;

@ValidRegexp(type = "password", message = "invalid password")
private String pass;