Java 校验注解的使用、自定义校验注解

一、引入依赖

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

<!--springboot 新版本需要validation启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
  1. 验证注解都在 javax.validation.constraints 这个包下
  2. 默认返回的异常信息存放在 ValidationMessages_zh_CN.properties 文件中

二、基本校验

1. 常用校验注解

注解类型说明
@NullObject被注释的元素必须为 null
@NotNullObject被注释的元素必须不为 null
@AssertTrueboolean/Boolean被注释的元素必须为 true
@AssertFalseboolean/Boolean被注释的元素必须为 false
@Min(value)BigDecimal/BigInteger/byte/short/int/long被注释数字必须大于等于指定的最小值
@Max(value)BigDecimal/BigInteger/byte/short/int/long被注释数字必须小于等于指定的最大值
@Range(min=,max=)BigDecimal/BigInteger/byte/short/int/long@Min 和 @Max 注解的合并
@DecimalMin(value)BigDecimal/BigInteger/byte/short/int/long被注释的数字必须大于等于指定的最小值
@DecimalMax(value)BigDecimal/BigInteger/byte/short/int/long被注释的数字必须小于等于指定的最大值
@Size(max=, min=)CharSequence/Collection/Map/Array被注释的集合、字符串的长度必须在指定的范围内
@Digits (integer, fraction)BigDecimal/BigInteger/byte/short/int/long被注释的元素必须是一个数字,且满足 integer 参数表示整数位数最大值,fraction 表示小数位数的最大值
@PastDate/Calendar被注释的日期必须是一个过去的日期
@FutureDate/Calendar被注释的日期必须是一个将来的日期
@Pattern(regex=,flag=)String被注释的字符串必须符合指定的正则表达式
@NotBlankString验证字符串非null,且长度必须大于0
@Length(min=,max=)String被注释的字符串的长度必须在指定的范围内
@NotEmptystring/collection/map/array被注释的字符串或集合不为空
@EmailString被注释的字符串必须是电子邮箱地址

2. 自动校验

1)定义一个实体类

public class Student {
    @Min(1)
    private Integer id;

    @NotBlank
    private String name;

    @Range(min = 0, max = 200)
    private Integer age;

    @Min(1)
    @Digits(fraction = 10, integer = 2)
    private BigDecimal money;
}

2)在请求时加上注解 @Valid

@PostMapping(value = "/createStudent")
public R save(@Valid @RequestBody Student student){
}

3. 代码中获取校验结果

加上 @valid 后台验证,在验证的参数后加 BindingResult 就可以获取验证结果,不获取结果会抛出 MethodArgumentNotValidException 异常

@PostMapping(value = "/createStudent")
public R save(@Valid @RequestBody Student student, BindingResult result){
    if (result.hasErrors()) {
        Map<String , String > map = new HashMap<>();
        result.getFieldErrors().forEach(item ->{
            String msg = item.getDefaultMessage();
            String field = item.getField();
            map.put(field, msg);
        });
        return R.error(400, "提交的数据不合法").put("data", map);
    }

	studentService.save(student);

    return R.ok();
}

三、嵌套校验

当实体类的字段为实体类或者为 List<Object> 时,要使用嵌套校验才可以校验内部的实体类, 要在字段上再加 @Valid 注解

@Data
public class AdInfo {
    /**
     * 区域统计数据
     */
    @Valid
    @NotNull(message = "arrival不能为空")
    private Arrival arrival;
    /**
     * 客户群统计数据
     */
    @Valid
    @NotNull(message = "departure不能为空")
    private List<Departure> departure;
}

四、分组校验

  1. 新建两个接口

    public interface AddGroup {
    }
    
    public interface UpdateGroup {
    }
    
  2. 使用 @Validated 注解来指定使用哪个分组的校验规则,如果不指定,则只会校验没有指定分组规则,所有有 group 参数的规则都不会校验

    /**
     * 保存
     */
    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
    	brandService.save(brand);
        return R.ok();
    }
    
    /**
     * 修改
     */
    @RequestMapping("/update")
    //@RequiresPermissions("product:brand:update")
    public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
    	brandService.updateById(brand);
        return R.ok();
    }
    
  3. 通过 groups 参数给字段上的注解分组,可以指定多个

    @Data
    @TableName("pms_brand")
    public class BrandEntity implements Serializable {
    	private static final long serialVersionUID = 1L;
    
    	/**
    	 * 品牌id
    	 */
    	@NotNull(message = "修改必须指定品牌id", groups = {UpdateGroup.class})
    	@Null(message = "新增不能指定id", groups = {AddGroup.class})
    	@TableId
    	private Long brandId;
    	/**
    	 * 品牌名
    	 */
    	@NotBlank(message = "品牌名必须提交", groups = {AddGroup.class, UpdateGroup.class})
    	private String name;
    	/**
    	 * 品牌logo地址
    	 */
    	@URL(message = "logo必须是合法的URL地址", groups = {AddGroup.class, UpdateGroup.class})
    	@NotEmpty(groups = {AddGroup.class})
    	private String logo;
    	/**
    	 * 介绍
    	 */
    	private String descript;
    	/**
    	 * 显示状态[0-不显示;1-显示]
    	 */
    	@ListValue(vals = {0, 1}, groups = {AddGroup.class})
    	private Integer showStatus;
    	/**
    	 * 检索首字母
    	 */
    	@Pattern(regexp = "/^[a-zA-Z]$/", message = "检索首字母必须是一个字母", groups = {AddGroup.class, UpdateGroup.class})
    	@NotEmpty(groups = {AddGroup.class})
    	private String firstLetter;
    	/**
    	 * 排序
    	 */
    	@Min(value = 0, message = "排序必须大于等于0", groups = {AddGroup.class, UpdateGroup.class})
    	@NotNull(groups = {AddGroup.class})
    	private Integer sort;
    }
    

五、单属性自定义校验规则

1. 功能:只能取枚举的值

1)定义自定义校验器,实现 ConstraintValidator 接口

  • 第一个泛型:注解
  • 第二个泛型:修饰的字段类型
    public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
    
        private Set<Integer> set = new HashSet<>();
    
    	/**
    	 * 初始化方法,一般用于将自定义注解中的参数缓存
    	 * @param constraintAnnotation  参数是自定义的注解
    	 */
        @Override
        public void initialize(ListValue constraintAnnotation) {
            int[] vals = constraintAnnotation.vals();
            for (int val : vals) {
                set.add(val);
            }
        }
    
        /**
         *
         * @param value     传过来的参数
         * @param context
         * @return		  	true 校验通过
         */
        @Override
        public boolean isValid(Integer value, ConstraintValidatorContext context) {
            return set.contains(value);
        }
    }
    

2)定义注解

  • 通过 @Constraint(validatedBy = {ListValueConstraintValidator.class}) 来指定上一步定义的校验器,可以指定多个校验器
  • 注解必须有三个参数,messagegroupspayload
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface ListValue {
	//这里指定 ValidationMessages.properties 文件中的键,一般是以注解的类路径来命名
	//当校验失败时默认返回的消息
    String message() default "{com.atguigu.common.valid.ListValue.message}";
    //分组校验时使用
    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    int[] vals() default {};
}

3)指定默认的错误提示 message,在 resource 中新建 ValidationMessages.properties 文件

com.atguigu.common.valid.ListValue.message="参数校验错误"
  • 可能出现编码错误,在 File > Settings > Editor > File Encodings,添加 ValidationMessages.properties,指定编码为UTF-8,并勾选Transparent native-to-ascii conversion,不行就重新新建该文件。参考这篇博客

4)修饰字段

/**
 * 显示状态[0-不显示;1-显示]
 */
@ListValue(vals = {0, 1}, groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;

六、多属性自定义联合校验规则

1. 功能:不同优惠券类型校验不同参数

1)实体类中自定义方法,将所有关联校验的字段都统一返回

public class CouponTmplDto{
    /**
     * 优惠券模板类型(0 满减券,1折扣券)
     */
    @NotNull
    private Integer couponTmplType;


    /**
     * 满多少钱,规则类型为“满减券”时必填
     */
    private BigDecimal fullMoney;


    /**
     * 减多少钱,规则类型为”满减券“时必填
     */
    private BigDecimal reduceMoney;


    /**
     * 折扣率,规则类型为“折扣券”时必填
     */
    private BigDecimal discountRate;

    /**
     * 注意:返回的方法要以 get 开头
     *
     * @return 所有所有要关联校验的参数
     */
    @JsonIgnore
    @CouponTmplTypeAnno(message = "优惠券类型校验失败")
    public Map<String, Object> getCouponTmplTypeValidator() {
        Map<String, Object> map = new HashMap();
        map.put("couponTmplType", couponTmplType);
        map.put("fullMoney", fullMoney);
        map.put("reduceMoney", reduceMoney);
        map.put("discountRate", discountRate);
        return map;
    }
}

2)自定义校验注解

@Documented
@Constraint(validatedBy = {CouponTmplTypeValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RUNTIME)
public @interface CouponTmplTypeAnno {

    String message() default "优惠券模板类型及金额参数校验失败";

    Class<?>[] groups() default {};
    
    Class<? extends Payload>[] payload() default {};

}

3)自定义校验器

import cn.hutool.core.util.BooleanUtil;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.math.BigDecimal;
import java.util.Map;

public class CouponTmplTypeValidator implements ConstraintValidator<CouponTmplTypeAnno, Map<String, Object>> {

    /**
     * 自定义校验器初始化
     *
     * @param constraintAnnotation 
     */
    @Override
    public void initialize(CouponTmplTypeAnno constraintAnnotation) {
        //注解没有参数,不需要初始化
    }

    /**
     * 校验方法
     *
     * @param map     注解在方法上时,该值是方法返回值;注解在字段上时,该值是字段值
     * @param context
     * @return true时校验通过,false时校验不通过
     */
    @Override
    public boolean isValid(Map<String, Object> map, ConstraintValidatorContext context) {
        Integer couponTmplType = (Integer) map.get("couponTmplType");
        BigDecimal fullMoney = (BigDecimal) map.get("fullMoney");
        BigDecimal reduceMoney = (BigDecimal) map.get("reduceMoney");
        BigDecimal discountRate = (BigDecimal) map.get("discountRate");
        switch (CouponTmplTypeEnum.getByType(couponTmplType)) {
            case FULL_REDUCT:
                //满减券 满a减b a>b
                return BooleanUtil.and(fullMoney != null, reduceMoney != null, fullMoney.compareTo(reduceMoney) > 0);
            case DISCOUNT:
                //折扣券 1位小数
                return discountRate != null && discountRate.scale() <= CommonConstant.ONE;
            default:
                return false;
        }
    }
}

2. 通用多属性联合校验注解

如果多属性联合校验的条件不通用,每次校验都要写校验器、注解会很麻烦,这里使用匿名内部类的方式,实现每次只需要写校验逻辑即可,不用再定义校验器和注解

1)通用自定义注解:

@Documented
@Constraint(validatedBy = {CustomValidator.class})
@Target({ElementType.METHOD})
@Retention(RUNTIME)
public @interface CustomValidateAnno {

    @AliasFor("message")
    String value() default "{com.snbc.coupon.activity.annotation.CustomValidateAnno.message}";

    /**
     * Message string.
     *
     * @return the string
     */
    @AliasFor("value")
    String message() default "{com.snbc.coupon.activity.annotation.CustomValidateAnno.message}";

    /**
     * Groups class [ ].
     *
     * @return the class [ ]
     */
    Class<?>[] groups() default {};

    /**
     * Payload class [ ].
     *
     * @return the class [ ]
     */
    Class<? extends Payload>[] payload() default {};

}

2)通用校验器:

public class CustomValidator implements ConstraintValidator<CustomValidateAnno, BooleanSupplier> {

    /**
     * 自定义校验器初始化
     *
     * @param constraintAnnotation annotation instance for a given constraint declaration
     */
    @Override
    public void initialize(CustomValidateAnno constraintAnnotation) {
        //不需要初始化
    }

    @Override
    public boolean isValid(BooleanSupplier supplier, ConstraintValidatorContext context) {
    	//这里使用匿名内部类,实现校验器和注解通用
        return supplier.getAsBoolean();
    }

}

3)使用注解:(优化上一节中优惠券的校验规则)

public class CouponTmplDto {
    /**
     * 优惠券模板类型(0 满减券,1折扣券)
     */
    @NotNull
    private Integer couponTmplType;


    /**
     * 满多少钱,规则类型为“满减券”时必填
     */
    private BigDecimal fullMoney;


    /**
     * 减多少钱,规则类型为”满减券“时必填
     */
    private BigDecimal reduceMoney;


    /**
     * 折扣率,规则类型为“折扣券”时必填
     */
    private BigDecimal discountRate;

    /**
     * 注意:返回的方法要以 get 开头
     *
     * @return 校验逻辑的匿名内部类, 校验通过(true), 校验不通过(false)
     */
    @CustomValidateAnno
    public BooleanSupplier getCouponTmplTypeValidateResult() {
        return () -> {
            switch (CouponTmplTypeEnum.getByType(couponTmplType)) {
                case FULL_REDUCT:
                    //满减券 满a减b a>b
                    return BooleanUtil.and(fullMoney != null, reduceMoney != null, fullMoney.compareTo(reduceMoney) > 0);
                case DISCOUNT:
                    //折扣券 1位小数
                    return discountRate != null && discountRate.scale() <= CommonConstant.ONE;
                default:
                    return false;
            }
        };
    }

    /**
     * 注意:返回的方法要以 get 开头
     *
     * @return 校验逻辑的匿名内部类, 校验通过(true), 校验不通过(false)
     */
    @CustomValidateAnno("校验失败时提示消息")
    public BooleanSupplier getOtherValidateResult() {
        return () -> {
            System.out.println("写校验方法");
            // true 校验通过
            return true;
        };
    }
}

七、手动校验

手动校验 registerInfo 这个对象是否符合规范

Set<ConstraintViolation<RegisterInfo>> validate = Validation.buildDefaultValidatorFactory().getValidator().validate(registerInfo);
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值