Spring MVC 使用 Validation 注解实现请求参数自动校验

序言

控制器方法( handler )中负责请求参数校验。

传统的写法是在每个方法的开头对所有请求参数进行校验,如果存在参数不满足条件,则抛出异常,或者提前返回错误。

Validation API 提供了一系列的注解,通过将这些注解标记在 entity 类的属性上,Spring MVC 会根据这些注解的语义,自动完成请求参数校验。如果参数不满足条件,则抛出异常。这节省了开发者手动校验请求参数的麻烦。

负责自动参数校验的类实际上是由 Spring Boot 自动装配导入的,因此我们要使用对应的 starter 依赖。

案例演示

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

validation-api 是本次介绍的重点

image-20230705200537754

项目结构

一个标准的 Spring Initializer 生成的 Spring Boot 项目

image-20230705200916180

entity

User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.gustav.springmvcvalidatordemo.entity;

import lombok.Data;

import javax.validation.constraints.*;

@Data
public class User {
@NotBlank
@Size(max = 5, message = "名字不能超过5个字符")
private String name;

@NotNull
@Min(value = 0, message = "年龄不能小于0")
@Max(value = 200, message = "年龄不能大于200")
private Integer age;
}

@NotBlank:用在 CharSequence 类型的属性上,表示该字段非空,且长度至少为 1。简单来说,该字符串必须有内容

@Size:用在CharSequence和集合类的属性上,表示该字段的字符数/元素数目。max = 5是数量的上界,不能超过该上界。当属性值不满足条件时,Spring MVC 会抛出异常,如果设置了message属性,则异常内容中会包含 message 的值

@NotNull:字面含义,字段非空

@Min@Max:用在数值类型上,对数值的大小进行约束。

Pet

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.gustav.springmvcvalidatordemo.entity;

import lombok.Data;

import javax.validation.constraints.NotBlank;

@Data
public class Pet {
@NotBlank
private String type;
@NotBlank
private String name;
}

UserAndPet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.gustav.springmvcvalidatordemo.entity;

import lombok.Data;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

@Data
public class UserAndPet {
@NotNull
@Valid // 这个注解保证了User类内部的字段也能被校验
private User user;

@NotNull
@Valid
private Pet pet;
}

@Valid注解告诉 Spring MVC,它标记的类上也有字段需要校验。

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.gustav.springmvcvalidatordemo.Controller;

import org.gustav.springmvcvalidatordemo.entity.UserAndPet;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@Controller
public class DemoController {
@PostMapping("/test")
@ResponseBody
public UserAndPet test(@Validated @RequestBody UserAndPet userAndPet) {
System.out.println(userAndPet);
return userAndPet;
}
}

@Validated指示 Spring MVC:当前请求参数需要被校验

使用 Postman 发送请求

合法请求

请求体如下:

1
2
3
4
5
6
7
8
9
10
{
"user": {
"name": "adam",
"age": 15
},
"pet": {
"type": "cat",
"name": "east wind"
}
}
image-20230705202651511

非法请求

故意使user.name超过长度限制。请求体如下:

1
2
3
4
5
6
7
8
9
10
{
"user": {
"name": "adam williams",
"age": 15
},
"pet": {
"type": "cat",
"name": "east wind"
}
}

服务器返回了 400 错误

image-20230705202748075

服务端抛出了MethodArgumentNotValidException的异常。内容是user.name为非法参数,同时展示了我们自己设置的 message

image-20230705202916086

总结

通过使用 Validation 注解,我们可以便捷地实现请求参数校验,无需写很多代码。

试想,我们使用传统方式校验请求参数,光是校验 user.name,就需要写很多代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@PostMapping("/test")
@ResponseBody
public UserAndPet test(@Validated @RequestBody UserAndPet userAndPet) {
if (userAndPet == null) {
throw new IllegalArgumentException();
}
if (userAndPet.getUser() == null) {
throw new IllegalArgumentException();
}
String name = userAndPet.getUser().getName();
if (!(name != null && name.length() >= 1 && name.length() <= 5)) {
throw new IllegalArgumentException();
}
// 校验其他请求参数

// 业务逻辑
}

当然,方便快捷的 Validation 注解校验建立在 Spring MVC 和 Spring Boot 的基础上。Spring Boot 具体自动装配了哪些类,Spring MVC 又是如何使用这些类完成请求参数校验,就不在本文的讨论范围内了。有兴趣的读者可以自行阅读源码,或者阅读其他文章。