java自定义约束注解

在大多数项目中,无论是 Bean Validation 定义的约束,还是 Hibernate Validator 附加的约束,都是无法满足我们复杂的业务场景。所以,我们需要自定义约束。这个很重要。

开发自定义约束一共只要两步:1)编写自定义约束的注解;2)编写自定义的校验器 ConstraintValidator 。

下面,就让我们一起来实现一个自定义约束,用于校验参数必须在枚举值的范围内。

IntArrayValuable

// IntArrayValuable.java

public interface IntArrayValuable {

    /**
     * @return int 数组
     */
    int[] array();

}

因为对于一个枚举类来说,我们无法获得它具体有那些值。所以,我们会要求这个枚举类实现该接口,返回它拥有的所有枚举值。

GenderEnum

cn.iocoder.springboot.lab22.validation.constants 包路径下,创建 GenderEnum 枚举类,枚举性别。代码如下:

// GenderEnum.java
public enum GenderEnum implements IntArrayValuable {

 MALE(1, "男"),
 FEMALE(2, "女");

 /**
 * 值数组
 */
 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(GenderEnum::getValue).toArray();

 /**
 * 性别值
 */
 private final Integer value;
 /**
 * 性别名
 */
 private final String name;

 GenderEnum(Integer value, String name) {
 this.value = value;
 this.name = name;
 }

 public Integer getValue() {
 return value;
 }

 public String getName() {
 return name;
 }

 @Override
 public int[] array() {
 return ARRAYS;
 }

}
  • 实现 IntArrayValuable 接口,返回值数组 ARRAYS

@InEnum

cn.iocoder.springboot.lab22.validation.core.validator 包路径下,创建 @InEnum 自定义约束的注解。代码如下:


// InEnum.java
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = InEnumValidator.class)
public @interface InEnum {

 /**
 * @return 实现 IntArrayValuable 接口的
 */
 Class value();

 /**
 * @return 提示内容
 */
 String message() default "必须在指定范围 {value}";

 /**
 * @return 分组
 */
 Class[] groups() default {};

 /**
 * @return Payload 数组
 */
 Class[] payload() default {};

 /**
 *  Defines several {@code @InEnum} constraints on the same element.
 */
 @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @interface List {

 InEnum[] value();

 }

}

  • 在类上,添加 @@Constraint(validatedBy = InEnumValidator.class) 注解,设置使用的自定义约束的校验器
  • value() 属性,设置实现 IntArrayValuable 接口的类。这样,我们就能获得参数需要校验的值数组。
  • message() 属性,设置提示内容。默认为 "必须在指定范围 {value}"
  • 其它属性,复制粘贴即可,都可以忽略不用理解。

5.4 InEnumValidator

cn.iocoder.springboot.lab22.validation.core.validator 包路径下,创建 InEnumValidator 自定义约束的校验器。代码如下:

// InEnumValidator.java

public class InEnumValidator implements ConstraintValidator {

 /**
 * 值数组
 */
 private Set values;

 @Override
 public void initialize(InEnum annotation) {
 IntArrayValuable[] values = annotation.value().getEnumConstants();
 if (values.length == 0) {
 this.values = Collections.emptySet();
 } else {
 this.values = Arrays.stream(values[0].array()).boxed().collect(Collectors.toSet());
 }
 }

 @Override
 public boolean isValid(Integer value, ConstraintValidatorContext context) {
 // <2.1> 校验通过
 if (values.contains(value)) {
 return true;
 }
 // <2.2.1>校验不通过,自定义提示语句(因为,注解上的 value 是枚举类,无法获得枚举类的实际值)
 context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
 context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()
 .replaceAll("\\{value}", values.toString())).addConstraintViolation(); // 重新添加错误提示语句 
 return false; // <2.2.2.>
 }

}
  • 实现 ConstraintValidator 接口。
    • 第一个泛型为 A extends Annotation ,设置对应的自定义约束的注解。例如说,这里我们设置了 @InEnum 注解。
    • 第二个泛型为 T ,设置对应的参数值的类型。例如说,这里我们设置了 Integer 类型。
  • 实现 #initialize(annotation) 方法,获得 @InEnum 注解的 values() 属性,获得值数组,设置到 values 属性种。
  • 实现 #isValid(value, context) 方法,实现校验参数值,是否在 values 范围内。
    • <2.1> 处,校验参数值在范围内,直接返回 true ,校验通过。
    • <2.2.1> 处,校验不通过,自定义提示语句。
    • <2.2.2> 处,校验不通过,所以返回 false

至此,我们已经完成了自定义约束的实现。下面,我们来进行下测试。

5.5 UserUpdateGenderDTO

cn.iocoder.springboot.lab22.validation.dto 包路径下,创建 UserUpdateGenderDTO 类,为用户更新性别 DTO。代码如下:

// UserUpdateGenderDTO.java
public class UserUpdateGenderDTO {

 /**
 * 用户编号
 */
 @NotNull(message = "用户编号不能为空")
 private Integer id;

 /**
 * 性别
 */
 @NotNull(message = "性别不能为空")
 @InEnum(value = GenderEnum.class, message = "性别必须是 {value}")
 private Integer gender;

 // ... 省略 set/get 方法
}
  • gender 字段上,添加 @InEnum(value = GenderEnum.class, message = "性别必须是 {value}") 注解,限制传入的参数值,必须在 GenderEnum 枚举范围内。

5.6 UserController

修改 UserController 类,增加修改性别 API 接口。代码如下:

// UserController.java
@PostMapping("/update_gender")
public void updateGender(@Valid UserUpdateGenderDTO updateGenderDTO) {
 logger.info("[updateGender][updateGenderDTO: {}]", updateGenderDTO);
}

模拟请求该 API 接口,响应结果如下:[图片上传失败...(image-d870d6-1699111506363)]

因为我们传入的请求参数 gender 的值为 null ,显然不在 GenderEnum 范围内,所以校验不通过,输出 "性别必须是 [1, 2]"

你可能感兴趣的:(java自定义约束注解)