当前位置: 代码网 > it编程>编程语言>Java > SpringBoot参数校验@Validated、@Valid方式详解

SpringBoot参数校验@Validated、@Valid方式详解

2025年11月13日 Java 我要评论
springboot参数校验@validated、@valid(javax.validation)一、应用场景在实际开发中,前端校验并不安全,任何人都可以通过接口来调用我们的服务,就算加了一层toke

springboot参数校验@validated、@valid(javax.validation)

一、应用场景

在实际开发中,前端校验并不安全,任何人都可以通过接口来调用我们的服务,就算加了一层token的校验,有心人总会转空子,来传各式各样错误的参数,如果后端不校验,导致数据库数据混乱、特别是关于金额的数据,可能一个接口把公司都给干倒了

二、原生参数校验

返回类(可以不用看)

/**
 * 用于返回
 * @param <t>
 */
@apimodel("统一返回类")
public class results<t> {
    public static final string error = "500";
    public static final string success = "200";

    /**
     * 返回码
     */
    @apimodelproperty("返回码,正确码为:200")
    private string rescode ;

    /**
     * 返回消息
     */
    @apimodelproperty("返回消息")
    private string msg ;

    /**
     * 返回实体
     */
    @apimodelproperty("返回实体")
    private t obj;

    public static <t> results<t> success(){
        return success(success,"成功",null);
    }

    public static <t> results<t> success(string msg){
        return success(success,msg,null);
    }

    public static <t> results<t> success(t obj){
        return success(success,"成功",obj);
    }

    public static <t> results<t> success(string msg,t obj){
        return success(success,msg,obj);
    }

    public static <t> results<t> success(string rescode,string msg,t obj){
        results<t> result = new results<t>();
        result.setrescode(rescode);
        result.setmsg(msg);
        result.setobj(obj);
        return result;
    }

    public static <t> results<t> failed() {
        return failed(error,"失败",null);
    }

    public static <t> results<t> failed(string msg) {
        return failed(error,msg,null);
    }

    public static <t> results<t> failed(string msg,t obj) {
        return failed(error,msg,obj);
    }

    public static <t> results<t> failed(string rescode,string msg) {
        return failed(rescode,msg,null);
    }

    public static <t> results<t> failed(integer rescode,string msg) {
        return failed(string.valueof(rescode),msg);
    }

    public static <t> results<t> failed(string rescode,string msg,t obj) {
        results<t> result = new results<t>();
        result.setrescode(rescode);
        result.setmsg(msg);
        result.setobj(obj);
        return result;
    }

    public static <t> results<t> failednopermission() {
        return failed(90005,"没有权限");
    }
    public static <t> results<t> failednopermission(string msg) {
        return failed(90005,msg);
    }


    public static <t> results<t> failedparameterexception() {
        return failed(90004,"参数异常");
    }
    public static <t> results<t> failedparameterexception(string msg) {
        return failed(90004,msg);
    }

    public static <t> results<t> failedloginexception() {
        return failed(90002,"登录失败");
    }
    public static <t> results<t> failedloginexception(string msg) {
        return failed(90002,msg);
    }

    public string getrescode() {
        return rescode;
    }

    public void setrescode(string rescode) {
        this.rescode = rescode;
    }

    public string getmsg() {
        return msg;
    }

    public void setmsg(string msg) {
        this.msg = msg;
    }

    public t getobj() {
        return obj;
    }

    public void setobj(t obj) {
        this.obj = obj;
    }

    @override
    public string tostring() {
        return "results{" +
                "rescode='" + rescode + '\'' +
                ", msg='" + msg + '\'' +
                ", obj=" + obj +
                '}';
    }
}

实体类

@apimodel("测试 validation 入参")
@data
public class testdto {
    @apimodelproperty(value = "名字",required = true)
    private string name;
    @apimodelproperty(value = "年龄",required = true)
    private integer age;
    @apimodelproperty(value = "爱好",required = true)
    private list<string> hobbies;
}

服务层(为了方便,我直接跟controller写在一起了)

我们可以看见如果参数过大,要一个一个筛选条件十分浪费时间

@restcontroller
// lombok 的日志注解
@slf4j
// swagger 的注解
@api("测试")
public class testcontroller {

    @postmapping("/testvalidation")
	// swagger 的注解
    @apioperation("测试 validation")
    public results testvalidation(@requestbody testdto dto){
        try {
            log.info("test 入参 dto={}",dto);
            // 这要一个一个的塞,很浪费时间
            if (dto.getname() == null || "".equals(dto.getname().trim())){
                return results.failed("名字不能为空");
            }
            if (dto.getage() == null){
                return results.failed("年龄不能为空");
            }
            if (dto.gethobbies() == null || dto.gethobbies().size() == 0){
                return results.failed("爱好不能为空");
            }
            return results.success();
        } catch (exception e) {
            log.error("test 报错",e);
            return results.failed();
        }
    }
}

三、使用javax.validation进行参数校验

导包

        <dependency>
            <groupid>javax.validation</groupid>
            <artifactid>validation-api</artifactid>
            <version>2.0.1.final</version>
        </dependency>
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-validation</artifactid>
        </dependency>

全局异常处理类

springboot开发中,在进行 入参校验 时,会抛出异常,最终全局统一捕获异常。

  • 表单绑定到 java bean 出错时,会抛出 bindexception 异常
  • 将请求体解析并绑定到 java bean 时,如果出错,则抛出 methodargumentnotvalidexception 异常
  • 普通参数(非 java bean)校验出错时,会抛出 constraintviolationexception 异常
@restcontrolleradvice
public class exceptioncontrolleradvice {

    /**
     * 将请求体解析并绑定到 java bean 时,如果出错
     * 表单绑定到 java bean 出错
     * 校验参数 @requestbody 时的异常
     * 注意 @validated 需要放在 自定义实体类(入参) 的类上
     * 例如 :
     * public results testupdate(@validated(update.class) @requestbody testdto dto){}
     */
    @responsestatus(httpstatus.ok)
    @exceptionhandler(methodargumentnotvalidexception.class)
    public results methodargumentnotvalidexceptionhandler(methodargumentnotvalidexception e) {
        // 从异常对象中拿到objecterror对象
        bindingresult br = e.getbindingresult();
        if (br.hasfielderrors()) {
            list<fielderror> fielderrorlist = br.getfielderrors();
            list<string> errors = new arraylist<>(fielderrorlist.size());
            for (fielderror error : fielderrorlist) {
                errors.add(error.getfield() + ":" + error.getdefaultmessage());
            }
            // 然后提取错误提示信息进行返回
            return results.failed(errors.tostring());
        }
        // 然后提取错误提示信息进行返回
        return results.failed("校验错误");
    }

    /**
     * 表单绑定到 java bean 出错
     *
     * @param e
     * @return
     */
    @responsestatus(httpstatus.ok)
    @exceptionhandler(bindexception.class)
    public results methodargumentnotvalidexceptionhandler(bindexception e) {
        // 从异常对象中拿到objecterror对象
        bindingresult br = e.getbindingresult();
        if (br.hasfielderrors()) {
            list<fielderror> fielderrorlist = br.getfielderrors();
            list<string> errors = new arraylist<>(fielderrorlist.size());
            for (fielderror error : fielderrorlist) {
                errors.add(error.getfield() + ":" + error.getdefaultmessage());
            }
            // 然后提取错误提示信息进行返回
            return results.failed(errors.tostring());
        }
        // 然后提取错误提示信息进行返回
        return results.failed("校验错误");
    }

    /**
     * 普通参数(非 java bean)校验出错
     * 校验参数 @requestparam @pathvariable 时的异常
     * 注意 @validated 需要放在controller的类上
     * 例如:
     *
     * @restcontroller
     * @requestmapping("/cs")
     * @validated public class testcontroller {
     * @postmapping("/test") public results test(
     * @pattern(regexp = "^\\d{19}$", message = "用户id,应为19位数字") string id,
     * @notblank(message = "名字不能为空") string name
     * ) {}
     * }
     */
    @responsestatus(httpstatus.ok)
    @exceptionhandler(constraintviolationexception.class)
    public results constraintviolationexception(constraintviolationexception e) {
        set<constraintviolation<?>> violations = e.getconstraintviolations();
        if (collectionutils.isempty(violations)) {
            log.error("constraintviolationexception violations 为空", e);
            return results.failed();
        }
        map<string, string> map = violations.stream()
                .collect(collectors.tomap(o -> {
                    pathimpl x = (pathimpl) o.getpropertypath();
                    return x.getleafnode().tostring();
                }, constraintviolation::getmessage, (k1, k2) -> k1));
        return results.failed(map.tostring());
    }
}

如果不加这个全局处理类,只会给前端返回这样的参数

加上全局配置类

实体类

@apimodel("测试 validation 入参")
@data
public class testdto {
    
    @apimodelproperty(value = "名字",required = true)
    // 适用于 string 类型的数据上,加了@notblank 注解的参数不能为 null 且 trim() 之后 size > 0,必须有实际字符
    @notblank(message = "名字不能为空")
    private string name;
    
    @apimodelproperty(value = "年龄",required = true)
    @notnull(message = "年龄不能为空")
    // 适用于基本数据类型(integer,long,double等等),当 @notnull 注解被使用在 string 类型的数据上,则表示该数据不能为 null(但是可以为 empty)
    private integer age;
    
    @apimodelproperty(value = "爱好",required = true)
    // 适用于 string、collection集合、map、数组等等,加了@notempty 注解的参数不能为 null 或者 长度为 0
    @notempty(message = "年龄不能为空")
    private list<string> hobbies;
}

服务层(为了方便,我直接跟controller写在一起了)

必须要加上 @valid 或者 @validated,后续我会讲解这两个有什么不同,目前来说,都可以用,但推荐用 @validated

@restcontroller
@slf4j
@api("测试")
public class testcontroller {

    @postmapping("/testvalidation")
    @apioperation("测试 validation")
    // 必须要加上 @valid 或者 @validated
    public results testvalidation(
        // 必须要加上 @valid 或者 @validated
        @valid @requestbody testdto dto){
        try {
            log.info("test 入参 dto={}",dto);
            return results.success();
        } catch (exception e) {
            log.error("test 报错",e);
            return results.failed();
        }
    }
}

测试

四、javax.validation包下其它常用的校验注解:

这个颜色的是常用的

注解含义
@null任何类型 必须为null
@notblank字符串、字符 字符类不能为null,且去掉空格之后长度大于
@notnull任何类型 不能为null
@length(min = 6, max = 8, message = “密码长度为6-8位。”)字符串的大小必须在指定的范围内
@notempty适用于 string、collection集合、map、数组等等,加了@notempty 注解的参数不能为 null 或者 长度为 0
@asserttrueboolean、boolean 布尔属性必须是true
@assertfalseboolean、boolean 布尔属性必须是false
@min(10)必须是一个数字,其值必须大于等于指定的最小值(我这填的是10)(整型)
@max(10)必须是一个数字,其值必须小于等于指定的最大值(我这填的是10)(整型)
@decimalmin(“10”)必须是一个数字,其值必须大于等于指定的最小值(我这填的是10)(字符串,可以是小数)
@decimalmax(“10”)必须是一个数字,其值必须小于等于指定的最大值(我这填的是10)(字符串,可以是小数)
@size(max = 10,min = 1)集合 限定集合大小
@digits(integer = 3, fraction = 2, message = “请输入有效的数字”)
private double number;
@digits 用于验证数字的整数位数和小数位数。该注解的 integer 和 fraction 属性分别用于指定整数位数和小数位数的限制。
integer 属性用于指定数字的最大整数位数。它是一个整数值,表示数字允许的最大整数位数。例如,integer = 3 表示数字最多可以有三位整数部分。
fraction 属性用于指定数字的最大小数位数。它是一个整数值,表示数字允许的最大小数位数。例如,fraction = 2 表示数字最多可以有两位小数部分。
@past时间、日期 必须是一个过去的时间或日期
@future时期、时间 必须是一个未来的时间或日期
@email字符串 必须是一个邮箱格式
@pattern(regexp = “[a-za-z]*”, message = “密码不合法”)字符串、字符 正则匹配字符串
@range(max = 150, min = 1, message = “年龄范围应该在1-150内。”)数字类型(原子和包装) 限定数字范围(长整型)
@url(protocol=,host=, port=,regexp=, flags=)被注释的字符串必须是一个有效的url
@creditcardnumber被注释的字符串必须通过luhn校验算法,银行卡,信用卡等号码一般都用luhn计算合法性
@scriptassert(lang=, script=, alias=)要有java scripting api 即jsr 223 (“scripting for the javatm platform”)的实现
@safehtml(whitelisttype=, additionaltags=)classpath中要有jsoup包

五、@validated 与 @valid 比较

文字讲解

spring validation验证框架对参数的验证机制提供了@validated(spring’s jsr-303规范,是标准jsr-303的一个变种),javax提供了@valid(标准jsr-303规范),配合bindingresult可以直接提供参数验证结果。

@valid属于javax.validation包下,是jdk给提供的 是使用hibernate validation的时候使用

@validated是org.springframework.validation.annotation包下的,是spring提供的 是只用spring validator校验机制使用

说明:java的jsr303声明了@valid这类接口,而hibernate-validator对其进行了实现

@validation对@valid进行了二次封装,在使用上并没有区别,但在分组、注解位置、嵌套验证等功能上有所不同,这里主要就这几种情况进行说明。

在检验controller的入参是否符合规范时,使用@validated或者@valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同:

  1. 分组:

@validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制,这个网上也有资料,不详述。@valid:作为标准jsr-303规范,还没有吸收分组的功能。

  1. 注解地方:

@validated:用在类型、方法和方法参数上。但不能用于成员属性(field)

@valid:可以用在方法、构造函数、方法参数和成员属性(field)上 所以可以用@valid实现嵌套验证

总结:

@valid 和 @validated 两者都可以对数据进行校验,待校验字段上打的规则注解(@notnull, @notempty等)都可以对 @valid 和 @validated 生效;

@valid 进行校验的时候,需要用 bindingresult 来做一个校验结果接收。当校验不通过的时候,如果手动不 return ,则并不会阻止程序的执行;

@validated 进行校验的时候,当校验不通过的时候,程序会抛出400异常,阻止方法中的代码执行,这时需要再写一个全局校验异常捕获处理类,然后返回校验提示。

总体来说,@validated 使用起来要比 @valid 方便一些,它可以帮我们节省一定的代码,并且使得方法看上去更加的简洁。

代码讲解,groups属性

在开发中,新增、修改两个接口,一般关系就在于新增时id可以为空,修改时id不能为空,那我们如果要使用 validation 用于参数校验,创建两个实体类就非常的不划算,这时

①、创建一个update接口

import javax.validation.groups.default;

public interface update extends default {
}

②、修改实体类

@apimodel("测试 validation 入参")
@data
public class testdto {
    @apimodelproperty(value = "id",required = true)
    // 新增时id为空,修改时id不能为空
    @notnull(message = "id不能为空",groups = update.class)
    private integer id;
    @apimodelproperty(value = "名字",required = true)
    @notblank(message = "名字不能为空")
    private string name;
}

③、服务层(为了方便,我直接跟controller写在一起了)

@restcontroller
@slf4j
@api("测试")
public class testcontroller {

    @postmapping("/testadd")
    @apioperation("测试 新增")
    public results testadd(@validated @requestbody testdto dto){
        try {
            log.info("testadd 入参 dto={}",dto);
            return results.success();
        } catch (exception e) {
            log.error("testadd 报错",e);
            return results.failed();
        }
    }

    @postmapping("/testupdate")
    @apioperation("测试 新增")
    public results testupdate(@validated(update.class) @requestbody testdto dto){
        try {
            log.info("testupdate 入参 dto={}",dto);
            return results.success();
        } catch (exception e) {
            log.error("testupdate 报错",e);
            return results.failed();
        }
    }
}

⑤、测试

  • 新增:

  • 修改:

六、校验list

错误案例

仅在外层包一个@valid是校验不了list中实体类的参数的

    @responsebody
    @postmapping("/testlist")
    public results testlist(@requestbody @valid list<testdto> dto){
        try {
            log.info("testlist 入参 dto={}",dto);
            return results.success();
        } catch (exception e) {
            log.error("testlist 报错",e);
            return results.failed();
        }
    }

方法其实有两种,我就只讲比较优雅且方便的方法

创建validlist类(可以直接复制使用)

/**
 * 可被校验的list
 *
 * @param <e> 元素类型
 * @author deolin
 */
@data
public class validlist<e> implements list<e> {
    @valid
    private list<e> list = new arraylist<>();
    @override
    public int size() {
        return list.size();
    }
    @override
    public boolean isempty() {
        return list.isempty();
    }
    @override
    public boolean contains(object o) {
        return list.contains(o);
    }
    @override
    public iterator<e> iterator() {
        return list.iterator();
    }
    @override
    public object[] toarray() {
        return list.toarray();
    }
    @override
    public <t> t[] toarray(t[] a) {
        return list.toarray(a);
    }
    @override
    public boolean add(e e) {
        return list.add(e);
    }
    @override
    public boolean remove(object o) {
        return list.remove(o);
    }
    @override
    public boolean containsall(collection<?> c) {
        return list.containsall(c);
    }
    @override
    public boolean addall(collection<? extends e> c) {
        return list.addall(c);
    }
    @override
    public boolean addall(int index, collection<? extends e> c) {
        return list.addall(index, c);
    }
    @override
    public boolean removeall(collection<?> c) {
        return list.removeall(c);
    }
    @override
    public boolean retainall(collection<?> c) {
        return list.retainall(c);
    }
    @override
    public void clear() {
        list.clear();
    }
    @override
    public e get(int index) {
        return list.get(index);
    }
    @override
    public e set(int index, e element) {
        return list.set(index, element);
    }
    @override
    public void add(int index, e element) {
        list.add(index, element);
    }
    @override
    public e remove(int index) {
        return list.remove(index);
    }
    @override
    public int indexof(object o) {
        return list.indexof(o);
    }
    @override
    public int lastindexof(object o) {
        return list.lastindexof(o);
    }
    @override
    public listiterator<e> listiterator() {
        return list.listiterator();
    }
    @override
    public listiterator<e> listiterator(int index) {
        return list.listiterator(index);
    }
    @override
    public list<e> sublist(int fromindex, int toindex) {
        return list.sublist(fromindex, toindex);
    }
}

测试

controller:

    @responsebody
    @postmapping("/testlist")
    public results testlist(@requestbody @validlist list<testdto> dto){
        try {
            log.info("testlist 入参 dto={}",dto);
            return results.success();
        } catch (exception e) {
            log.error("testlist 报错",e);
            return results.failed();
        }
    }

经过我们写的全局异常处理类,得出的结果

七、自定义 枚举校验注解

注解

/**
 * 枚举校验注解
 */
@target({method, field, annotation_type, constructor, parameter})
@retention(runtime)
@documented
@constraint(validatedby = {enumvaluevalidator.class})
public @interface enumvalue {

    // 默认错误消息
    string message() default "必须为指定值";

    string[] strvalues() default {};

    int[] intvalues() default {};

    // 分组
    class<?>[] groups() default {};

    // 负载
    class<? extends payload>[] payload() default {};

    // 指定多个时使用
    @target({field, method, parameter, annotation_type})
    @retention(runtime)
    @documented
    @interface list {
        enumvalue[] value();
    }
}

枚举校验注解处理类

/**
 * 枚举校验注解处理类
 */
public class enumvaluevalidator implements constraintvalidator<enumvalue, object> {

    private string[] strvalues;
    private int[] intvalues;

    @override
    public void initialize(enumvalue constraintannotation) {
        strvalues = constraintannotation.strvalues();
        intvalues = constraintannotation.intvalues();
    }

    @override
    public boolean isvalid(object value, constraintvalidatorcontext context) {
        if (value instanceof string) {
            for (string s : strvalues) {
                if (s.equals(value)) {
                    return true;
                }
            }
        } else if (value instanceof integer) {
            for (int s : intvalues) {
                if (s == ((integer) value).intvalue()) {
                    return true;
                }
            }
        }
        return false;
    }
}

使用

@data
@accessors(chain = true)
public class test {
    @enumvalue(strvalues = {"1", "2", "3"}, message = "传入类型不正确")
    private string type;
    @enumvalue(intvalues = {1, 2, 3}, message = "传入类型不正确")
    private string status;
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com