SPRING VALIDATION

Spring Validation的作用

Spring Validation的主要作用是检查请求参数的基本格式。

关于检查请求参数

需要注意:在开发实践中,无论是客户端的项目(例如网页前端),还是服务器端的项目,都需要对用户填写、选择的数据进行检查!

其实,最终能够保证数据有效的一定是服务器端的检查,所以,服务器端必须对请求参数进行检查,仅当数据的基本格式有效后,才进行相关的处理。

客户端的检查应该是不作为最终保障的,在前后端分离的模式下,服务器端无法保证所有客户端都采取了统一、有效的验证规则!因为客户端的检查可能并未实现、
用户设备中客户端软件的版本并未升级,甚至,客户端软件是可能伪造、篡改的。

即使客户端的检查不一定是可靠的,但是,所有客户端仍应该对请求参数进行检查,如果参数的基本格式不符合要求,则不应该提交请求!毕竟客户端的检查可以把
绝大部分错误拦截掉(不会低级错误的请求发到服务器端),以减轻服务器端的压力。

添加依赖

在Spring Boot项目中,使用Spring Boot Validation的依赖项是:

1
2
3
4
5
<!-- Spring Boot Validation:用于检查请求参数的基本格式 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

基本使用

在控制器处理请求的方法中,对需要验证的请求参数添加@Valid@Validated注解,表示此参数是需要通过Spring Boot
Validation进行检查的,例如:

1
2
3
4
5
6
7
@PostMapping("/add-new")
// ================================== @Valid =============================
public JsonResult addNew(@RequestBody @Valid BrandAddNewDTO brandAddNewDTO) {
log.debug("接收到的请求参数:{}", brandAddNewDTO);
brandService.addNew(brandAddNewDTO);
return JsonResult.ok();
}

在封装了请求参数的POJO类中,在各需要验证格式的属性之前,添加检查格式的注解,即可实现格式的验证,例如:

1
2
3
4
5
6
/**
* 品牌名称
*/
@ApiModelProperty(value = "品牌名称", required = true, example = "华为")
@NotNull // 新增
private String name;

处理异常

当请求参数不符合注解对应的规则时,将抛出org.springframework.validation.BindException,所以,应该在统一处理异常的类中,对此异常进行处理。

先在ServiceCode中添加新的业务状态码,对应请求参数格式错误:

1
2
3
4
/**
* 错误:数据格式有误
*/
public static final int ERR_BAD_REQUEST = 40000;

然后,GlobalExceptionHandler中对此异常进行处理:

1
2
3
4
5
@ExceptionHandler
public JsonResult handleBindException(BindException e) {
log.error("统一处理BindException,将向客户端响应:{}", e.getMessage());
return JsonResult.fail(ServiceCode.ERR_BAD_REQUEST, e.getMessage());
}

BindException的错误信息(message)中,将包含大量的信息,不利于响应到客户端去,例如,以上处理方式的响应大概是:

1
2
3
4
5
{
"code": 40000,
"message": "Validation failed for argument [0] in public cn.tedu.csmall.server.web.JsonResult cn.tedu.csmall.server.controller.BrandController.addNew(cn.tedu.csmall.server.pojo.dto.BrandAddNewDTO): [Field error in object 'brandAddNewDTO' on field 'name': rejected value [null]; codes [NotNull.brandAddNewDTO.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [brandAddNewDTO.name,name]; arguments []; default message [name]]; default message [不能为null]] ",
"data": null
}

为了保证处理异常后响应的消息是易于阅读的,应该对错误信息(message)进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
@ExceptionHandler
public JsonResult handleBindException(BindException e) {
log.error("统一处理BindException,将向客户端响应:{}", e.getMessage());
// String message = e.getBindingResult().getFieldErrors().get(0).getDefaultMessage();
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
StringJoiner stringJoiner = new StringJoiner(";", "", "。");
for (FieldError fieldError : fieldErrors) {
stringJoiner.add(fieldError.getDefaultMessage());
}
String message = stringJoiner.toString();
return JsonResult.fail(ServiceCode.ERR_BAD_REQUEST, message);
}

关于检查参数格式的注解

  • @NotNull:不允许为null,即必须提交此名称对应的参数

    • 注意:应用于字符串格式的参数,更适合:基本数据类型
  • @NotEmpty:不允许为空字符串(长度为0的字符串)

    • 注意:应用于字符串格式的参数,更适合:数组或集合属性的判断(不能为null、不能为空)
  • @NotBlank:不允许为空白(字符串长度可能大于0,却是由空格、TAB等空白输入的)

    • 注意:应用于字符串格式的参数
  • @Min:设置数值类型的最小值

    • 仅用于数值类型的请求参数
    • 可被@Range取代
  • @Max:设置数值类型的最大值

    • 仅用于数值类型的请求参数
    • 可被@Range取代
  • @Range:设置数据类型的取值区间,可设置最小值和最大值

    • 此注解的min属性默认为0max属性默认为long类型的最大值
    • 仅用于数值类型的请求参数
  • @Pattern:配置正则表达式

    • 不能取代@NotNull注解
  • 其它

在开发实践中,对于字符串类型的请求参数,应该同时使用@NotNull(如果你认为必须提交)和@Pattern,而@NotEmpty@NotBlank
通常不需要使用,除非你对此字符串的值没有太多要求,对于数值类型的请求参数,应该同时使用@NotNull@Range

需要注意:对于数值类型的属性,如果提交的请求参数不能够转换成数值类型,则这些注解将不生效,且报错。

另外,在开发实践中,通常会将使用到的正则表达式、出错时的提示文本封装在专门的类或接口中,各POJO类型直接引用即可。

同时使用@NotNull和@NotEmpty,不提交name
同时使用@NotNull和@NotEmpty,提交name:“”
只使用@NotEmpty,不提交name
只使用@NotNull,提交name:“”
@NotBlank - 不提交、提交name:“”、提交name:" "