Spring Boot中使用JSR-303实现请求参数校验

news2024/9/22 11:28:16

JSR-303是Java中的一个规范,用于实现请求参数校验。它定义了一组注解,可以应用于JavaBean的字段上,用于验证输入参数的合法性。下面是一些常用的JSR-303注解及其介绍:

@NotNull:用于验证字段值不能为null。
@NotEmpty:用于验证字符串字段不能为空。
@NotBlank:用于验证字符串字段不能为空,并且长度必须大于0。
@Min:用于验证数字字段的最小值。
@Max:用于验证数字字段的最大值。
@Size:用于验证字符串、集合或数组字段的长度或大小。
@Pattern:用于验证字符串字段是否匹配指定的正则表达式。
@Email:用于验证字符串字段是否符合Email格式。
@Range:用于验证数字字段的取值范围。
@Valid:用于嵌套验证,可以对对象中的字段进行递归验证。
其他如下图所示:
在这里插入图片描述

通过在JavaBean的字段上添加这些注解,可以在接收请求参数时进行自动校验。如果校验失败,可以通过异常处理机制来处理校验错误。

需要注意的是,JSR-303只提供了基本的验证注解,如果需要更复杂的校验逻辑,可以自定义注解或使用第三方库,如Hibernate Validator等。
Hibernate Validator附加的constraint:
在这里插入图片描述

至于我们为啥使用JSR-303来校验我们的参数,主要是为了解决我们在实际开发过程中,前后端的参数校验导致的问题。比如:

  1. 我们依靠前端框架解决参数校验,但是缺少服务器的参数校验,这种情况常见于需要同时进行开发前后端的时候,虽然程序的正常使用不会有问题,到那时开发者会忽略非正常的操作,比如绕过前端程序,直接模拟客户端请求,这样就绕过了我们对前端预设的各种限制,直击我们后端的数据访问接口,进而使得我们的后端系统存在严重的安全隐患。
  2. 大量的利用if/else语句嵌套实现,使得我们的校验逻辑晦涩难懂,并不利于我们对整个系统的维护。

JSR的定义标准

验证触发时机:

  • JSR-303校验标准定义了两个触发校验的时机:

    • 在对象被持久化之前(例如,保存到数据库之前)。
    • 在对象被修改之后(例如,更新数据库中的记录后)。
  • 校验约束注解:JSR-303标准提供了一组注解,可以用于对Java对象的属性进行校验。这些注解包括但不限于@NotNull@NotEmpty@Min@Max@Size@Pattern@Email 等。开发人员可以根据需求选择适当的注解来定义校验规则。

  • 校验组:JSR-303允许将校验规则分组,以便在特定情况下选择性地执行校验。开发人员可以为每个校验注解指定一个或多个校验组,然后在校验时选择要执行的校验组。

  • 嵌套校验:JSR-303允许对复杂对象进行嵌套校验,即在校验一个对象时,也会对其关联的其他对象进行校验。这样可以确保整个对象图的完整性和合法性。

  • 自定义校验:JSR-303还允许开发人员定义自己的校验注解和校验器,以满足特定的校验需求。通过实现 ConstraintValidator 接口来自定义校验器,并在自定义注解中使用该校验器。

  • 校验结果:JSR-303校验结果以校验异常的形式返回。当校验失败时,会抛出 ConstraintViolationException 异常,其中包含了校验失败的详细信息,例如校验失败的属性、校验失败的值、校验错误消息等。

接下来我们将围绕我们在Spring Boot的实体类中,使用JSR-303校验。值得注意的是,JSR-303校验我们一般都是对Java的实体类对象进行校验,主要检验在我们的实体类对象的属性上。

还是利用我们==>上一篇 <===的相关依赖文件,这篇我就不在重复导入相关依赖文件了。本篇着重文件主要正在User类和UserController,所用的依赖pom.xml文件和application.properties均与上篇一样,就不在重复描述了。

PS: 你可以看到我在依赖中均采用了lombok,但是我却并未使用这个玩意,原因在于,我使用了lombok

@ApiModel(description = "用户实体")
public class User {

    @ApiModelProperty("用户编号")
    private Long id;

    @NotNull //校验定义的字段不能为空
    @Size(min = 2, max = 5)
    @ApiModelProperty("用户姓名")
    private String name;

    @NotNull
    @Max(100)
    @Min(10)
    @ApiModelProperty("用户年龄")
    private Integer age;

    @NotNull
    @Email
    @ApiModelProperty("用户邮箱")
    private String email;

    public User(Long id, String name, Integer age, String email) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.email = email;
    }
    public User() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id) && Objects.equals(name, user.name) && Objects.equals(age, user.age) && Objects.equals(email, user.email);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, age, email);
    }
}
@Api(tags = "用户管理")
@RestController
@RequestMapping(value = "/users")     // 通过这里配置使下面的映射都在/users下
public class UserController {

    // 创建线程安全的Map,模拟users信息的存储
    static Map<Long, User> users = Collections.synchronizedMap(new HashMap<>());

    @GetMapping("/")
    @ApiOperation(value = "获取用户列表")
    public List<User> getUserList() {
        List<User> r = new ArrayList<>(users.values());
        return r;
    }

    @PostMapping("/")
    @ApiOperation(value = "创建用户", notes = "根据User对象创建用户")
    public String postUser(@Valid @RequestBody User user) {
        users.put(user.getId(), user);
        return "success";
    }

    @GetMapping("/{id}")
    @ApiOperation(value = "获取用户详细信息", notes = "根据url的id来获取用户详细信息")
    public User getUser(@PathVariable Long id) {
        return users.get(id);
    }

    @PutMapping("/{id}")
    @ApiImplicitParam(paramType = "path", dataType = "Long", name = "id", value = "用户编号", required = true, example = "1")
    @ApiOperation(value = "更新用户详细信息", notes = "根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息")
    public String putUser(@PathVariable Long id, @RequestBody User user) {
        User u = users.get(id);
        u.setName(user.getName());
        u.setAge(user.getAge());
        users.put(id, u);
        return "success";
    }

    @DeleteMapping("/{id}")
    @ApiOperation(value = "删除用户", notes = "根据url的id来指定删除对象")
    public String deleteUser(@PathVariable Long id) {
        users.remove(id);
        return "success";
    }
}

接下来启动项目,当然启动类还是需要加:
@EnableSwagger2Doc这个注解

之后,我们可以通过使用相关工具比如Postman等测试工具发起,也可以使用curl发起,比如:
在这里插入图片描述

curl -X POST \
  http://localhost:8080/users/ \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: 114db0f0-bdce-4ba5-baf6-01e5104a68a3' \
  -H 'cache-control: no-cache' \
  -d '{
    "name": "abcdefg",
    "age": 8,
    "email": "aaaa"
}'

得到相关信息:

{
    "timestamp": "2019-10-05T06:24:30.518+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "Size.user.name",
                "Size.name",
                "Size.java.lang.String",
                "Size"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.name",
                        "name"
                    ],
                    "arguments": null,
                    "defaultMessage": "name",
                    "code": "name"
                },
                5,
                2
            ],
            "defaultMessage": "个数必须在2和5之间",
            "objectName": "user",
            "field": "name",
            "rejectedValue": "abcdefg",
            "bindingFailure": false,
            "code": "Size"
        },
        {
            "codes": [
                "Min.user.age",
                "Min.age",
                "Min.java.lang.Integer",
                "Min"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.age",
                        "age"
                    ],
                    "arguments": null,
                    "defaultMessage": "age",
                    "code": "age"
                },
                10
            ],
            "defaultMessage": "最小不能小于10",
            "objectName": "user",
            "field": "age",
            "rejectedValue": 8,
            "bindingFailure": false,
            "code": "Min"
        },
        {
            "codes": [
                "Email.user.email",
                "Email.email",
                "Email.java.lang.String",
                "Email"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.email",
                        "email"
                    ],
                    "arguments": null,
                    "defaultMessage": "email",
                    "code": "email"
                },
                [],
                {
                    "defaultMessage": ".*",
                    "codes": [
                        ".*"
                    ],
                    "arguments": null
                }
            ],
            "defaultMessage": "不是一个合法的电子邮件地址",
            "objectName": "user",
            "field": "email",
            "rejectedValue": "aaaa",
            "bindingFailure": false,
            "code": "Email"
        }
    ],
    "message": "Validation failed for object='user'. Error count: 3",
    "path": "/users/"
}


其中返回名称的各参数含义如下:

timestamp:请求时间
status:HTTP返回的状态码,这里返回400,即:请求无效、错误的请求,通常参数校验不通过均为400
error:HTTP返回的错误描述,这里对应的就是400状态的错误描述:Bad Request
errors:具体错误原因,是一个数组类型;因为错误校验可能存在多个字段的错误,比如这里因为定义了两个参数不能为Null,所以存在两条错误记录信息
message:概要错误消息,返回内容中很容易可以知道,这里的错误原因是对user对象的校验失败,其中错误数量为2,而具体的错误信息就定义在上面的errors数组中
path:请求路径

从errors数组中各个错误明细,知道各个字段的defaultMessage,可以看到很清晰的错误描述。

浏览器访问:
http://localhost:8080/swagger-ui.html

在这里插入图片描述
在这里插入图片描述
我们可以看到校验的部分限制。

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

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

相关文章

RT-Thread 5. ENV添加自定义模块

代码 /* file: hello.c */ #include <stdio.h> #include <finsh.h> #include <rtthread.h> int hello_world(void) {rt_kprintf("Hello, world!\n");return 0; } MSH_CMD_EXPORT(hello_world, Hello world!)/* file: hello.h */ #ifndef _HELLO_H…

03 vi编辑器

vi编辑器的三种模式: 不同的模式下机键动作解释的意义是不一样的 编辑模式 插入模式 末行模式 文件的打开和关闭保存 移动光标

专业135总分400+西安交通大学信息与通信工程学院909/815考研经验分享

今年初试发挥不错&#xff0c;400&#xff0c;专业课135&#xff0c;将近一年复习一路走来&#xff0c;感慨很多&#xff0c;希望以下经历可以给后来的同学提供一些参考。 初试备考经验 公共课&#xff1a;三门公共课&#xff0c;政治&#xff0c;英语&#xff0c;数学。在备考…

AST反混淆实战|找出某里滑块226混淆代码隐藏的字符串

关注它&#xff0c;不迷路。 本文章中所有内容仅供学习交流&#xff0c;不可用于任何商业用途和非法用途&#xff0c;否则后果自负&#xff0c;如有侵权&#xff0c;请联系作者立即删除&#xff01; 1. 常见的字符串 在还原控制流之后&#xff0c;接下来的动作就是还原字…

高效翻译工具GPT插件的使用教程

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

【语义分割】语义分割概念及算法介绍

文章目录 一、基本概念二、研究现状2.1 传统算法2.2 深度学习方法 三、数据集及评价指标3.1 常用数据集3.2 常用指标 四、经典模型参考资料 一、基本概念 语义分割是计算机视觉中很重要的一个方向。不同于目标检测和识别&#xff0c;语义分割实现了图像像素级的分类。它能够将…

【【萌新的FPGA学习之Vivado下的仿真入门-2】】

萌新的FPGA学习之Vivado下的仿真入门-2 我们上一章大概了解了 我们所需要进行各项操作的基本框架 对于内部实现其实一知半解 我们先从基本的出发 但从FPGA 了解一下 vivado下的仿真入门 正好帮我把自己的riscV 波形拉一下 行为级仿真 step1: 进入仿真界面&#xff1a;SIMULAT…

凉鞋的 Unity 笔记 204. 语句

204. 语句 在上一篇&#xff0c;我们接触了三种常见的类型&#xff0c;如下所示&#xff1a; 这样我们算是对变量进行了一个入门年了。 其实我们除了变量&#xff0c;我们还接触了一个叫做语句的概念。 我们可以看下代码&#xff1a; using System.Collections; using Syst…

四川云汇优想教育咨询有限公司电商服务正规吗

随着抖音等短视频平台的火热&#xff0c;越来越多的消费者选择在平台上购物。四川云汇优想教育咨询有限公司也推出了抖音电商服务&#xff0c;但它的服务是否正规呢&#xff1f;本文将为您揭开真相。 首先&#xff0c;我们先来了解一下四川云汇优想教育咨询有限公司。这是一家致…

基于Java的足球赛会管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

C#,数值计算——分类与推理Phylo_wpgma的计算方法与源程序

1 文本格式 using System; using System.Collections.Generic; namespace Legalsoft.Truffer { public class Phylo_wpgma : Phylagglom { public override void premin(double[,] d, int[] nextp) { } public override double dminfn(double…

java基础篇-环境变量

java基础 编程学习的关键点、重点1.环境变量设置待续 编程学习的关键点、重点 输入输出 Java语言、C语言、Python语言、甚至SQL语言&#xff0c;都需要实战、做大量输入输出等 1.环境变量设置 1.下载jdk安装 jdk官网下载直达链接&#xff1a;https://www.oracle.com/java/te…

2.9.C++项目:网络版五子棋对战之业务处理模块的设计

文章目录 一、意义二、功能三、管理&#xff08;一&#xff09;客户端请求&#xff08;二&#xff09;websocket 四、框架五、完整代码 一、意义 将所有的模块整合在一起&#xff0c;通过网络通信获取到客户端的请求&#xff0c;提供不同的业务处理。 服务器模块&#xff0c;是…

类加载机制和双亲委派机制

文章目录 &#x1f4d5;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作者、产品软文创造者、技术文章评审老师、问卷调查设计师、个人社区创始人、开源项目贡献者。&#x1f30e;跑过十五…

winodos下使用VS2022编译eclipse-paho.mqtt.c并演示简单使用的 demo

本文演示C语言如何使用eclipse-paho.mqtt.c库&#xff0c;包含自行编译库的步骤或者下载编译好的文件。 1.下载paho.mqtt.c库源码&#xff08;zip 文件&#xff09; 到官网选择C版本的paho源码进行下载 Eclipse Paho | The Eclipse Foundation 或者到下述连接下载 Releases ec…

docker在java项目中打成tar包

docker在java项目中打成tar包 1、首先安装一个docker desktop 2、mvn install项目后&#xff0c;建立一个自己的dockerfile 这里我以我的代码举例&#xff0c;from 镜像&#xff0c;这里你也能打包好一个镜像的基础上&#xff0c;from打好的镜像&#xff0c;这里我们用openj…

【C语言初阶】 一文详解分支语句 if

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《速学C语言》《数据结构篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言结构化程序设计的三种基本结构&#x1f4ac; 分支语句 &#xff08;选择结构&#xff09;&#x1f4d1; if 语句…

平衡二叉树(AVL)

平衡二叉树 基本介绍 左旋转调整成平衡二叉树 右旋转调整成平衡二叉树 双旋转调整成平衡二叉树 上述三种旋转方式的代码实现 class Node:"""创建 Node 节点"""value: int 0left Noneright Nonedef __init__(self, value: int):self.value …

HFS 快速搭建 http 服务器

HFS 是一个轻量级的HTTP 服务工具&#xff0c;3.0版本前进提供Windows平台安装包&#xff0c;3.0版本开提供Linux和macOS平台的安装包。 HFS更适合在局域网环境中搭建文件共享服务或者安装配置源服务器。 甲 非守护进程的方式运行 HFS &#xff08;Ubuntu 22.04&#xff09; 一…

c语言进制的转换2进制转换16进制

c语言进制的转换2进制转换16进制 c语言的进制的转换 c语言进制的转换2进制转换16进制一、16进制的介绍二、八四二一法则2进制转换16进制的方法 一、16进制的介绍 十六进制&#xff1a; 十六进制逢十六进一&#xff0c;所有的数组是0到9和A到F组成&#xff0c;其中A代表10&…