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


    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    @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";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    接下来启动项目,当然启动类还是需要加:
    @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"
    }'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    得到相关信息:

    {
        "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/"
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97

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

    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

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

  • 相关阅读:
    一些感悟(随笔小记)
    信息检索 | 信息检索概述
    计算机毕业设计(41)java小程序毕设作品之在线教育视频学习小程序系统
    大模型的规模扩展是否可持续?
    聊聊ChatGLM-6B部署与微调的深入理解
    BS框架说明
    操作系统八股文背诵版
    IDEA报错:前言中不允许有内容
    深入探索Java中的MarkWord与锁优化机制——无锁、偏向锁、自旋锁、重量级锁
    对网络流的一个小总结
  • 原文地址:https://blog.csdn.net/qq_45922256/article/details/134026851