SpringBoot参数自定义校验
通过Hibernate的可以对一些基础数据进行校验,但是在真实的业务场景下,我们的验证是针对复杂的业务逻辑进行验证而不单单是对基础数据的验证。举个例子,用户在注册的时候,用户要输入两次密码,一次是原密码,一次是确认密码,两次密码一致才允许用户进行注册,那么这种需要比较两个字段相等的验证如何来写?
1、自定义元注解
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface PasswordEqual { String message() default "passwords are not equal"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
其中,@Documented、@Retention(RetentionPolicy.RUNTIME)、@Target({ElementType.TYPE}) 这三个注解是元注解。这只是一个注解,并没有业务逻辑,业务逻辑的编写需要一个关联类
2、编写关联类
关联类必须要实现 ConstraintValidator<A, T> 接口, 这个接口接收的两个泛型的参数,其中第一个参数是注解的类型,这里就是 PasswordEqual,第二个参数是自定义注解所修饰的目标的类型,就是说我们的自定义注解修饰的是哪个 DTO,这里传入的就是那个 DTO 的类型,例如我们要将该注解用在 PersonDTO 上,那么这里传入的就是 PersonDTO。
@PasswordEqual 修饰的 DTO:
@Builder @Getter @PasswordEqual public class PersonDTO { @Length(min = 3, max = 8, message = "姓名长度必须在3-8之间") private String name; @Max(120) private Integer age; @Valid private SchoolDTO schoolDTO; }
关联类(PasswordValidator)
/** * 自定义注解 @PasswordEqual 的关联类, 用于校验两次输入的面是否一致 * * ConstraintValidator 接口的第一个参数传入的是自定义注解的类型, * 第二个参数传入的是自定义注解修饰的目标的类型 * * 例如: * @PasswordEqual * public class PersonDTO { * private String name; * } * 那么第二个参数传入的就是 PersonDTO * * 例如: * public class PersonDTO { * @PasswordEqual * private String name; * } * 那么第二个参数传入的就是 String */ public class PasswordValidator implements ConstraintValidator<PasswordEqual, PersonDTO> { // 这里必须要实现 isValid(),校验逻辑就是这里实现 @Override public boolean isValid(PersonDTO personDTO, ConstraintValidatorContext constraintValidatorContext) { String password1 = personDTO.getPassword1(); String password2 = personDTO.getPassword2(); if (!StringUtils.isBlank(password1) && !StringUtils.isBlank(password2)) { return password1.equals(password2); } return false; } }
3、将自定义注解和关联类关联起来
在自定义注解上添加 @Constrain(validatedBy = PasswordValidator.class) 将他们关联起来,validatedBy 后面的值就是关联类名;这里的 validatedBy 后面是可以跟一个数组的,也就是说这里可以关联多个类
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Constraint(validatedBy = PasswordValidator.class) public @interface PasswordEqual { String message() default "passwords are not equal"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
到此,一个自定义的注解就可以使用了,但是在具体业务中,可能会在很多时候需要注解能传入参数的,类似 @Range(min = 1, max = 10, message = "长度不能只能在1-10之间") 这种的,那么就需要在自定义注解中添加这两个参数变量用于接收,然后在通过关联类进行逻辑处理即可。
4、实现接收参数的自定义注解
4.1 增加参数的自定义注解
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Constraint(validatedBy = PasswordValidator.class) public @interface PasswordEqual { // 注意在注解中只能使用基本类型不能使用包装类型 int min() default 1; int max() default 10; String message() default "passwords are not equal"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; // 关联类 }
注意:在自定义注解中只能使用基本类型不能使用包装类型,也就是只能使用 int 、boolean 这种格式的,而不能使用 Integer 和 Boolean 这种格式
4.2 关联类接收参数,并进行逻辑处理
关联类要获取参数就必须重新接口中的 initialize() 这个方法
/** * 自定义注解 @PasswordEqual 的关联类, 用于校验两次输入的面是否一致 * <p> * ConstraintValidator 接口的第一个参数传入的是自定义注解的类型, * 第二个参数传入的是自定义注解修饰的目标的类型 * <p> * 例如: * * @PasswordEqual public class PersonDTO { * private String name; * } * 那么第二个参数传入的就是 PersonDTO * <p> * 例如: * public class PersonDTO { * @PasswordEqual private String name; * } * 那么第二个参数传入的就是 String */ public class PasswordValidator implements ConstraintValidator<PasswordEqual, PersonDTO> { // 用于接收自定义注解中的参数 private int min; private int max; /** * 如果要实现接收参数,则必须要重写这个方法 * * @param constraintAnnotation 通过该参数的方法可以获取到自定义注解中的参数 */ @Override public void initialize(PasswordEqual constraintAnnotation) { this.min = constraintAnnotation.min(); this.max = constraintAnnotation.max(); } /** * 要将自定义注解和关联类关联起来必须要实现 isValid(),校验逻辑就是这里实现 * * @param personDTO 自定义注解修饰目标的类型 * @param constraintValidatorContext 关联类与注解 * @return 是否合法 */ @Override public boolean isValid(PersonDTO personDTO, ConstraintValidatorContext constraintValidatorContext) { String password1 = personDTO.getPassword1(); String password2 = personDTO.getPassword2(); if (password1.length() < this.min || password2.length() > this.max) { return false; } if (!StringUtils.isBlank(password1) && !StringUtils.isBlank(password2)) { return password1.equals(password2); } return false; } }
至此,整个自定义参数的写法就结束了