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

2023-11-11

一、引入依赖

<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. 常用校验注解

注解 类型 说明
@Null Object 被注释的元素必须为 null
@NotNull Object 被注释的元素必须不为 null
@AssertTrue boolean/Boolean 被注释的元素必须为 true
@AssertFalse boolean/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 表示小数位数的最大值
@Past Date/Calendar 被注释的日期必须是一个过去的日期
@Future Date/Calendar 被注释的日期必须是一个将来的日期
@Pattern(regex=,flag=) String 被注释的字符串必须符合指定的正则表达式
@NotBlank String 验证字符串非null,且长度必须大于0
@Length(min=,max=) String 被注释的字符串的长度必须在指定的范围内
@NotEmpty string/collection/map/array 被注释的字符串或集合不为空
@Email String 被注释的字符串必须是电子邮箱地址

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);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 校验注解的使用、自定义校验注解 的相关文章

随机推荐

  • 8-js高级-6(promise)

    一 Promise 的理解和使用 1 Promise 是什么 理解 抽象表达 Promise 是一门新的技术 ES6 规范 Promise 是 JS 中进行异步编程的新解决方案 备注 旧方案是单纯使用回调函数 具体表达 从语法上来说 Pro
  • c语言练习题56:变种水仙花

    变种水仙花 描述 变种水仙花数 Lily Number 把任意的数字 从中间拆分成两个数字 比如1461 可以拆分成 1和461 14和61 146和1 如果所有拆分后的乘积之和等于自身 则是一个Lily Number 例如 655 6 5
  • Echarts柱状图的点击事件

    最近在做一些图表统计的功能 用到了百度的开源图表软件Echatrs 不得不提的是 不但上手简单而且扩展功能也是十分强大 在使用的过程中也遇到了不少问题 可能由于有关Echatrs的资料并不是很齐全 所以查找资料的过程也是相当曲折的 所以还是
  • 硬盘错误计数 计算机内存不足,硬盘问题!Ultra DMA CRC错误计数 电脑死机

    最近电脑经常出现卡机状态 此状态出现前先是硬盘嗡嗡响 就像汽车油门一样 一加一松 但声音不是很大 然后硬盘紧接着还有嘎吱的响声 这样重复几次 出现这种声音的时候 电脑出现死机状态 但停上几分钟后 一切恢复正常 有时候也会卡到电脑自动重新启动
  • linux下sqlite3的使用实例(c语言)

    文章目录 1 安装数据库 2 相关函数 3 代码实例 3 1创建一个数据库 3 2插入数据 3 3查看表的内容 3 4删除数据 1 安装数据库 Linux 下安装sqlite3 需要两个个命令 即可 1 sudo apt get insta
  • Bootstrap Table行内添加/行内编辑案例

    项目场景 JQuery版本为 3 6 0 Bootstrap版本为 3 4 1 Bootstrap Table版本为 1 8 1 Bootstrap Table Edit版本为 1 0 Bootstrap Select版本为 1 0 Boo
  • c++排序算法(快速排序、冒泡排序、选择排序)

    1 快速排序 这里的容器是全局的 不全局的话 可以在参数那里加个数组的参数传进来 从大到小 从大到小排序 void ResManage quickSortLastUpdateTime const int iLeftIndex const i
  • 华为手机投屏电脑_华为手机如何实现无线投屏?

    朋友们 大家好 大家知道吗 其实我们风雪社是可以留言的 点击下面图片就可以跳转到留言界面哦 别担心 没有广告的 接下来 开始今天的内容了 大家都知道一些华为手机支持无线投屏 那用华为的朋友们 如何知道自己的手机是否可以投大屏幕呢 如果可以
  • Qt--02:设置父对象

    问题引入 在Widget中创建Button等控件时 如果不指定Button等控件的父对象 就会产生相关控件无法再此Widget窗口中显示 因此当一些控件需要在当前Widget窗口中显示的时候就需要为控件添加父对象 指定父对象方式 setPa
  • Spring Security 实现 antMatchers 配置路径的动态获取

    为什么80 的码农都做不了架构师 gt gt gt 1 为什么要实现动态的获取 antMatchers 配置的数据 这两天由于公司项目的需求 对 spring security 的应用过程中需要实现动态的获取 antMatchers per
  • mybatis防注入

    1 SQL防注入 mybatis语句中要使用 xxx 防止SQL注入 xxx 只是简单替换占位符 有注入的风险 例子 1 1 getNameByUserId resultType String gt SELECT name FROM use
  • HTML预留字符的处理

    目录 HTML 实体 不间断空格 non breaking space HTML 实例示例 HTML 中有用的字符实体 HTML 中的预留字符必须被替换为字符实体 HTML 实体 在 HTML 中 某些字符是预留的 在 HTML 中不能使用
  • JavaScript new Date() -2--------- 获取指定月份的天数 和 获取指定月份的第一天是周几

    使用 new Date 创建一个 Date 对象 var my date new Date 用当前日期和时间创建的新的日期对象 console log my date Fri Jan 10 2020 10 16 19 GMT 0800 中国
  • 电脑用电,电脑一天24小时多少度电 电脑一天用电量计算【图文】

    我们都知道 电器只要运行着 就会有一定的耗电量 比如我们平时的冰箱 空调 电视 电脑等等 今天小编便主要针对电脑来讲一讲 很多的朋友可能比较关注的都是电脑一天24小时耗用多少度电 还有因为我们想要自己来计算的话 应该要如何计算呢 还有 很多
  • DockerFile语法讲解

    Docker官网 https docs docker com Docker的github地址 https github com moby moby Dockerhub官网 https registry hub docker com 如果do
  • USB Type-C引脚解析 && CC、DFP、UFP、DRP用途解析【转】

    深入解析TypeC 1 Type C接口引脚分布 2 USB TypeC接口特点 3 DFP UFP DRP用途解析 4 深入解析CC通道 5 总结 1 Type C接口引脚分布 有24根信号 其中电源和地占了9根 有4个地和4个Vbus
  • 不定参数函数

    参数的存储位置 include
  • BES2300X,BES2500X——UI(按键,提示音,指示灯)

    前言 BES2300X BES2500X系列博文请点击这里 本文是BES2300X BES2500X系列博文UI部分 一个耳机 音箱 UI是联系使用者与开发者最直接的一个窗口 当然 对于吾等码农而言 UI设计并不是我们最关心的 我们主要做的
  • Altium Designer---PCB覆铜镂空

    AD版本 AD18 转到PCB界面 如下 在PCB页面切换到你想挖空区域所在图层 比如我这里想要挖空Top Layer和Bottom Layer 先切换到Top Layer 右击选择 多边形覆铜挖空 然后在PCB上绘制出想要挖空的区域 如下
  • Java 校验注解的使用、自定义校验注解

    文章目录 一 引入依赖 二 基本校验 1 常用校验注解 2 自动校验 3 代码中获取校验结果 三 嵌套校验 四 分组校验 五 单属性自定义校验规则 1 功能 只能取枚举的值 六 多属性自定义联合校验规则 1 功能 不同优惠券类型校验不同参数