Spring Validation 接口入参校验

news2024/12/24 8:18:51

一、前言

  • JSR 是 Java Specification Requests 的缩写,含义为 JAVA 规范提案。

  • JSR 303 - Bean Validation 规范, 正是一套基于 JavaBean 参数校验的标准。

  • Hibernate Validator 是 JSR 303 的实现,它提供了 JSR 303 规范中所有约束(constraint)的实现,同时也对其作出一些拓展。

  • Spring Validation 是对 Hibernate validation 的二次封装,用于支持 Spring MVC 参数校验。


😊 JSR 303 包含的注解:

验证注解

验证数据类型

说明

@Null

任意类型

元素值为 Null

@NotNull

任意类型

元素值不为 Null

@AssertTrue

Bool

元素为 true

@AssertFalse

Bool

元素为 false

@Min(value = 最小值)

BigDecimal、BigInteger、byte、short、int、long 等 Number 或 CharSequence (数字)子类型

元素值需大于等于指定值

@Max(value = 最大值)

BigDecimal、BigInteger、byte、short、int、long 等 Number 或 CharSequence (数字)子类型

元素值需小于等于指定值

@DecimalMin(value = 最小值)

BigDecimal、BigInteger、byte、short、int、long 等 Number 或 CharSequence (数字)子类型

元素值需大于等于指定值

@DecimalMax(value = 最大值)

BigDecimal、BigInteger、byte、short、int、long 等 Number 或 CharSequence (数字)子类型

元素值需小于等于指定值

@Size(min = 最小值, max = 最大值)

String、Collection、Array 等

元素值的字符长度/集合大小需在指定的区间范围内

@Digits(integer = 整数位数, fraction = 小数位数)

BigDecimal、BigInteger、byte、short、int、long 等 Number 或 CharSequence (数字)子类型

元素值整数位、小数位需小于对应指定值

@Past

DATE、Calendar、Time 等日期

元素值需在指定时间之前

@Future

DATE、Calendar、Time 等日期

元素值需在指定时间之后

@Pattern(regexp = 正则式, flag = 标志的模式)

String 等

元素值与指定的正则式匹配

😊hibernate.validator 扩展的注解:

验证注解

验证数据类型

说明

@NotBlank

String 等 CharSequence 子类型

元素值不为空串(字符串不为 Null 且去除首尾空格后长度不为 0)

@Email(regexp = 正则式, flag = 标志的模式)

String 等

元素值为电子邮箱格式,可通过 regexp、flag 属性指定格式

@Length(min = 最小值, max = 最大值)

String 等

元素值长度在指定区间范围内

@NotEmpty

String、Collection、Array 等

元素值不为 Null 且长度不为 0

@Range(min = 最小值, max = 最大值)

BigDecimal、BigInteger、byte、short、int、long 等 Number 或 CharSequence (数字)子类型

元素值在指定区间范围内

@URL

String 等

元素值必须时合法的URL

二、Spring Validation的使用

1、在项目pom.xml中引入依赖

<!-- JSR 303 -->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
</dependency>

<!-- 在 SpringBoot 项目中,若 SpringBoot 版本小于 2.3.x ,则此依赖已包含在 spring-boot-starter-web 中,无需添加额外依赖;
若 SpringBoot 版本大于 2.3.x ,则需手动引入依赖。-->
<!-- Hibernate Validator -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

2、 参数注解校验的使用场景

场景一:直接入参的校验

①在类上添加 @Validated 注解,否则参数校验无法生效;

②入参中使用注解 @NotEmpty 等;

@Validated
@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/saveUserName")
    public String saveUserInfo(@NotEmpty String userName) {
        System.out.println("userName:"+ userName +",保存成功");
        return "success";
    }
}

测试:

【请求URL】:
http://192.168.1.7:27100/user/saveUserName?userName=

【执行结果】:
严重: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is javax.validation.ConstraintViolationException: saveUserInfo.userName: 不能为空] with root cause
javax.validation.ConstraintViolationException: saveUserInfo.userName: 不能为空
    at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:116)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
……

场景二:对象模型属性的校验

1、创建 User对象

@Data
public class User {

    private Integer id;

    @NotEmpty(message = "用户名不能为空")
    private String userName;
    
    private Integer age;

    @NotNull(message = "用户密码不能为空")
    @Size(min = 5, max = 10,message = "密码长度必须是5-10个字符")
    private String password;
}

2、全局异常捕获

@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 方法直接入参校验
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public BaseResult resolveConstraintViolationException(ConstraintViolationException ex) {
        Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
        if (!CollectionUtils.isEmpty(constraintViolations)) {
            String errorMessage = constraintViolations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(", "));
            return BaseResult.fail(ResultEnum.ILLEGAL_PARAMETER, errorMessage);
        }
        return BaseResult.fail(ResultEnum.ILLEGAL_PARAMETER, ex.getMessage());
    }

    /**
     * 对象模型属性校验
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public BaseResult resolveMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        List<ObjectError> allErrors = ex.getBindingResult().getAllErrors();
        if (!CollectionUtils.isEmpty(allErrors)) {
            String errorMessage = allErrors.stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(", "));
            return BaseResult.fail(ResultEnum.ILLEGAL_PARAMETER, errorMessage);
        }
        return BaseResult.fail(ResultEnum.ILLEGAL_PARAMETER, ex.getMessage());
    }
}

3、通用返回结构体

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class BaseResult<T> {

    private Integer code;

    private String message;

    private T data;

    public static <T> BaseResult<T> success() {
        return new BaseResult(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMsg(), new JSONObject());
    }

    public static <T> BaseResult<T> fail(ResultEnum resultEnum, T data) {
        return fail(resultEnum.getCode(), resultEnum.getMsg(), data);
    }
    @Override
    public String toString() {
        return "BaseResult{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}

4、错误提示码枚举类

/**
 * 错误提示码
 */
public enum ResultEnum {
    /** 状态码:未知错误 **/
    UNKNOWN_ERROR(-1, "未知错误"),
    /** 状态码:成功 **/
    SUCCESS(0, "成功"),
    /** 状态码:失败 **/
    FAIL(1,"失败"),
    /** 状态码:非法参数 **/
    ILLEGAL_PARAMETER(2,"非法参数");

    private Integer code;

    private String msg;

    ResultEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

}

5、暴露的接口

① 在类上添加 @Validated 注解,否则参数校验无法生效;

② 在入参实体前添加 @Validated 注解;

@Validated
@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping("/saveUserInfo")
    public String saveUserInfo(@RequestBody @Validated User user) {
        System.out.println("userName:"+ user.getUserName() +",保存成功");
        return "success";
    }
}

6、测试:

【请求URL】:http://192.168.1.7:27100/user/saveUserInfo
【请求方式】:POST
【请求参数】:
{
   "userName":"张三"
}

【执行结果】:
{
     "code": 2,
     "message": "非法参数",
     "data": "用户密码不能为空"
}

场景三:参数注解校验的分组验证

1、新建分组接口

/**
 * 分组接口:新增
 */
public interface Add {

}

/**
 * 分组接口:更新
 */
public interface Update {

}

2、在校验注解中使用 groups 属性标明分组

@Data
public class User {

    @NotNull(message = "更新数据时,主键id不能为空", groups = Update.class)
    private Integer id;

    @NotEmpty(message = "用户名不能为空")
    private String userName;
    
    private Integer age;

    @NotNull(message = "用户密码不能为空")
    @Size(min = 5, max = 10,message = "密码长度必须是5-10个字符")
    private String password;
}

【注】需要注意的是实体类中所有字段注解校验规则默认属于 Default 分组,接口入参 @Validated 注解默认检验 Default 分组的校验规则。当接口入参使用 @Validated 显式声明非默认分组时,实体类中所有未显式声明分组的注解校验将不会生效。

3、处理器方法

@Validated
@RestController
@RequestMapping("/user")
public class UserController {
    @PostMapping("/updateUserInfo")
    public String updateUserInfo(@RequestBody @Validated({Update.class}) User user) {
        System.out.println("id:" + user.getId() + ",userName:"+ user.getUserName() +",更新成功");
        return "success";
    }
}

4、测试

【请求URL】:http://192.168.1.7:27100/user/updateUserInfo
【请求方式】:POST
【请求参数】:
{
  "id":1,
  "userName":"李四"
}

【执行结果】:
id:1,userName:李四,更新成功

【注】入参中没有传“密码”,也没有校验,若默认属于 Default 分组的字段也需要校验,可以写成:

public String updateUserInfo(@RequestBody @Validated({Update.class, Default.class}) User user) {

场景四:级联校验

1、在实体类模型中,可能会存在集合类型或实体类型的成员变量,该成员中的字段仍然需要校验。

在要校验的对象类型的属性/ list上使用 @Valid 注解:

@Data
public class User {

    @NotNull(message = "更新数据时,主键id不能为空", groups = Update.class)
    private Integer id;

    @NotEmpty(message = "用户名不能为空")
    private String userName;
    
    private Integer age;

    @NotNull(message = "用户密码不能为空")
    @Size(min = 5, max = 10,message = "密码长度必须是5-10个字符")
    private String password;

    @Valid
    private List<Car> cars;
}


@Data
public class Car {

    @NotNull(message = "车牌号码不能为空")
    private String plateCode; 

    @NotNull(message = "车牌颜色不能为空")
    private String plateColor;
}

2、处理器方法

@Validated
@RestController
@RequestMapping("/user")
public class UserController {
    @PostMapping("/saveUserInfo")
    public String saveUserInfo(@RequestBody @Validated User user) {
        System.out.println("userName:"+ user.getUserName() +",保存成功");
        return "success";
    }
}

3、测试

【请求URL】:http://192.168.1.7:27100/user/saveUserInfo
【请求方式】:POST
【请求参数】:
{
        
    "userName": "李四",
    "password": "123456",
    "cars": [
        {
            "plateCode": "京A0001",
            "plateColor": "1"
        },
                {
            "plateCode": "京A0002"
        }
    ]
}

【执行结果】:
{
     "code": 2,
     "message": "非法参数",
     "data": "车牌颜色不能为空"
}

场景五:自定义注释校验

如下:校验用户下车牌号码必须包含“京A”,否则返回提示;

1、自定义注释

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {MustContainKeyValidator.class})
public @interface MustContainKey {

    //默认错误信息
    String message() default "必须含有指定关键字";
 
   //分组
    Class<?>[] groups() default {};

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

2、真正的校验者类,实现 ConstraintValidator 接口

public class MustContainKeyValidator implements ConstraintValidator<MustContainKey,String> {
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (!StringUtils.isEmpty(value) && !value.contains("京A")) {
            // 获取默认提示消息
            String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
            System.out.println(defaultConstraintMessageTemplate);

            // 禁用默认提示信息
            // context.disableDefaultConstraintViolation();

            //设置提示语
            // context.buildConstraintViolationWithTemplate("must contain key").addConstraintViolation();

            return false;
        }
        return true;
    }
}

3、在实体类的属性上添加该注释

@Data
public class User {

    @NotNull(message = "更新数据时,主键id不能为空", groups = Update.class)
    private Integer id;

    @NotEmpty(message = "用户名不能为空")
    private String userName;
    
    private Integer age;

    @NotNull(message = "用户密码不能为空")
    @Size(min = 5, max = 10,message = "密码长度必须是5-10个字符")
    private String password;

    @Valid
    private List<Car> cars;
}


@Data
public class Car {
    
    @MustContainKey
    @NotNull(message = "车牌号码不能为空")
    private String plateCode; 

    @NotNull(message = "车牌颜色不能为空")
    private String plateColor;
}

4、测试

【请求URL】:http://192.168.1.7:27100/user/saveUserInfo
【请求方式】:POST
【请求参数】:
{
        
    "userName": "李四",
    "password": "123456",
    "cars": [
        {
            "plateCode": "京A0001",
            "plateColor": "1"
        },
        {
            "plateCode": "浙C0002",
            "plateColor": "2"
        }
    ]
}
【执行结果】:
{
     "code": 2,
     "message": "非法参数",
     "data": "必须含有指定关键字"
}

以上就是所有关于 Spring Validation 的介绍,感兴趣的同学欢迎点赞+收藏!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/611698.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

泪崩!测试面试技术面过了却挂在了——“谈谈你的职业生涯规划”

前不久&#xff0c;软件测试交流群里面有一个成员吐槽&#xff0c;说今天的面试技术已经面过了&#xff0c;可HR却问了她“未来的职业发展目标是什么&#xff1f;”然后&#xff0c;挂了&#xff01;这个问题我们平时在交流群里都有讲过&#xff0c;可是这丫头比较疯&#xff0…

级差制系统开发模式是怎么赚钱的?

级差制是直销所有模式中最受欢迎的模式之一&#xff0c;很多企业商家都会在级差制和双轨制中二选一&#xff0c;可见这个模式的优秀程度。下面就来简单分析一下&#xff0c;在级差制模式中是怎么赚钱的&#xff1f; 级差制最大的特点就是以卖货为主&#xff0c;它所有的奖金设置…

正规理财app软件有哪些?top5资质正规理财app软件最新排名

正规理财app软件有哪些&#xff1f;随着移动端理财的普及&#xff0c;越来越多的人开始使用理财app软件进行投资和资产管理。但是&#xff0c;市场上有很多理财软件&#xff0c;如何选择一款正规、安全的软件是关键。下面就为大家介绍一些选择理财app软件的建议。首先&#xff…

GDT陶瓷气体放电管串电容的5点作用

串电容&#xff0c;是指串联衔接于线路中&#xff0c;其主要目的是用来补偿电力线路感抗的电容器&#xff0c;电容器也是目前电力设备中必不可少的一环&#xff0c;其种类很多。下面优恩将为大家介绍一下GDT陶瓷气体放电管串电容的作用。 据小编了解&#xff0c;GDT陶瓷气体放电…

Bellhop 海底地形起伏条件下的传播特性

文章目录 前言一、预备内容二、水平海底波导&#xff08;水平海底&#xff09;1、海底水平的深海波导中的声线①、环境文件②、Matlab 命令③、执行结果 2、海底水平的深海波导中的本征声线①、环境文件②、Matlab 命令③、执行结果 3、海底水平的深海波导中的相干传播损失①、…

TDEngine3.0 环境安装、配置及使用经验总结

TDEngine3.0 环境安装、配置及使用经验总结 一、TDengine 介绍二、TDengine的下载三、TDengine Server安装及配置3.1 安装3.2 taos的参数配置3.3 启动3.4 taosAdapter 四、TDengine Client 安装4.1 linux客户端安装4.2 windows客户端安装 五、TDEngine3.x的使用总结 一、TDengi…

minhook探究

参考&#xff1a;https://github.com/TsudaKageyu/minhook minhook是windows平台上支持x86/x64的hook库&#xff0c;git上的自我介绍说是“mininalistic",其简约并不简单。在接口的设计&#xff0c;hook的兼容性等方面&#xff0c;还是值得我们初学者解决的。熟悉inline …

Hadoop之HDFS概述

Hadoop概述之HDFS HDFS架构概述优缺点HDFS架构HDFS文件块大小HDFS的shell命令HDFS读写流程写数据流程 HDFS读数据流程NameNode 和 SecondaryNameNode工作机制DataNode工作机制DataNode数据完整性如何保证 端口名称Hadoop2.xHadoop3.xNameNode内部通信端口8020/9000NameNode HTT…

两天搞定计算机专业毕业设计,附源码

两天搞定计算机专业毕业设计&#xff0c;附源码 适用者毕设专业 使用要求具备基本Unity 基本操作小白即可&#xff0c;无需编码 博主诉求快乐毕业 点赞 关注 收藏 资源说明Free资源太多了&#xff0c;看截图目录就知道了 适用者 毕设专业 鄙人也是计算机狗一只&#xff0c;会…

软考A计划-电子商务设计师-复习要点

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

Ampere 又放大招,推出自研192 核AmpereOne 系列处理器,已投产

作者 | 伍杏玲 近日&#xff0c;Ampere Computing 发布2023年度战略和产品路线图&#xff0c;并推出全新的AmpereOne系列处理器&#xff0c;拥有多达 192 个单线程 Ampere 核&#xff0c;内核数量为业界最高。这是第一款基于 Ampere 新自研核的产品&#xff0c;由 Ampere 自有…

java--正则表达式

一、作用 作用一&#xff1a;校验字符串是否满足规则 作用二&#xff1a;在一段文本中查找满足要求的内容 二、符号含义 1、字符类&#xff08;只匹配一个字符&#xff09; 符号含义[abc]只能是a,b或c中一个[^abc]除了a,b,c之外的任何字符[a-zA-Z]a到z A到Z[a-d[m-p]]a到d&…

弱网测试,Network Link Conditioner你知多少

网络环境的好坏&#xff0c;有时会让你的产品带给用户完全不同的体验&#xff0c;作为开发者&#xff0c;在开发项目过程中&#xff0c;我们需要进行对于网络环境的调试。Mac环境下模拟慢速网络可以使用苹果官方提供的工具&#xff1a; Network Link Conditioner 具体操作步骤…

数字化时代,公司如何成为数据驱动组织

当前&#xff0c;数据要素和数字经济提出了数据在生产过程中发挥的重要作用。其中最热点的话题包括数据资产、数据价值、数据驱动和数字化转型。如果数据是一种资产&#xff0c;那么它应该为公司及其利益相关者创造价值。那么如何获从数据中获得得不同类型的价值以维持公司的竞…

Python获取当当平台商品数据信息可视化效果展示

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 环境使用: 版 本&#xff1a; python 3.8 编辑器&#xff1a;pycharm 2021.2 jupyter notebook 模块使用: 采集 requests >>> pip install requests 数据请求 csv <表格文件> 内置模块 保存数据 …

图书购物商城 图书后台管理系统

图书购物商城 图书后台管理系统 这个图书购物商城系统是一个基于JSP、Servlet和MySQL技术开发的综合性系统&#xff0c;它包括前台和后台功能。该系统旨在为用户提供一个便捷的购书平台&#xff0c;同时为管理员提供管理图书和订单的功能。 前台部分是用户使用的界面&#xf…

MQTT与EMQ

文章目录 1 MQTT协议与EMQ中间件1.1 物联网消息协议MQTT1.1.1 什么是MQTT1.1.2 MQTT相关概念1.1.3 消息服务质量QoS——信息的可靠投递1.1.3.1 QoS0——消息服务质量为0&#xff0c;消息发送至多一次1.1.3.2 QoS1——消息发送至少一次1.1.3.3 QoS2——消息发送仅一次1.1.3.4 不…

app渗透-常见问题及绕过

app渗透-常见问题及绕过 6.app常见问题和绕过前言6.1反代理操作前言6.1.1判断6.1.2实例演示-探探6.1.3绕过1-r0capture6.1.4绕过2-proxifier6.1.5绕过3-小黄鸟 6.2证书校验前言6.2.1判断6.2.2浏览器校验和解决6.2.3桡过证书单项校验-xp框架6.2.3绕过证书双向校验 6.app常见问题…

TongWeb8适配JakartaEE应用

历史&#xff1a; 2017年 Oracle将Java EE&#xff08;Java SE还自己保留&#xff09;交给开源组织&#xff0c;Eclipse基金会接手。但Oracle不允许开源组织使用Java名号&#xff0c;所以Jakarta EE名称于2018.02.26应运而生。 版本 发布日期 焦点说明 Java EE 8 2017.08 …

30天网络安全从入门到精通?

前言 毫无疑问&#xff0c;网络安全是当下最具潜力的编程方向之一。对于许多未曾涉足计算机编程的领域「小白」来说&#xff0c;深入地掌握网络安全看似是一件十分困难的事。至于一个月能不能学会网络安全&#xff0c;这个要看个人&#xff0c;对于时间管理不是很高的&#xf…