自定义注解
先说一下元注解
- Target:
- Target 注解的作用是:描述注解的使用范围(即:被修饰的注解可以用在什么地方)
public enum ElementType {
TYPE, // 类、接口、枚举类
FIELD, // 成员变量(包括:枚举常量)
METHOD, // 成员方法
PARAMETER, // 方法参数
CONSTRUCTOR, // 构造方法
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE, // 注解类
PACKAGE, // 可用于修饰:包
TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
- @Retention 注解
- Reteniton 注解的作用是:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时)
- 一共有 3 种: SOURCE 源文件保留,CLASS 编译期保留(默认值),RUNTIME 运行时保留
- @Decumented 注解
- Documented 注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息
- @Inherited 注解
- Inherited 注解的作用是:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited 修饰的注解,则其子类将自动具有该注解)。
自定义注解校验
比如资定义一个正则表达式校验注解:
@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})
表示用在成员变量上
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 方法的定义:
public boolean matches(String regex) {
return Pattern.matches(regex, this);
}
initialize
方法会在验证器实例化后被调用。这里首先从 constraintAnno
获取类型名,然后从 config
对象中反射获取对应的字段,并从中提取出正则表达式。如果反射操作失败,会抛出一个运行时异常.
这个校验器的还有一个就是ValidatorConfig config
是什么? 这是在另外一个地方定义的:
@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 type
var 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。
为了进一步理解:
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
使用:
@ValidRegexp(type = "tel")
private String tel;
@ValidRegexp(type = "username", message = "invalid username")
private String name;
@ValidRegexp(type = "password", message = "invalid password")
private String pass;