日常搬砖中经常遇到需要对接口方法进行校验,除了常规的 if 条件判断,是否还有更加优雅的处理方法?我们经常从公众号或者其他博文上看到的可能就是  spring-boot-starter-validation 提供的方法,今天主要对这个操作进行详细讲解,提供可以实操的方案。

使用流程

1、需要的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2、在需要校验的实体类的字段上添加相应的校验注解。

public class User {

    @NotBlank(message = "用户名不能为空")
    private String username;
    
    @Size(min = 6, max = 12, message = "密码长度必须在6-12个字符之间")
    private String password;
    
    // getters and setters
}

3、在需要校验参数的Controller类或方法上添加@Validated注解。

@RestController
@Validated
public class UserController {

    @PostMapping("/users")
    public ResponseEntity createUser(@Valid @RequestBody User user) {
        // 校验通过,执行业务逻辑
        // ...
        return ResponseEntity.ok().build();
    }

}

通常我们一般看到的信息可能就到这里就完了,实际上要做的还有很多。因为上面的接口返回信息如下:

{
	"timestamp": "2024-03-08T01:42:23.301+0000",
	"status": 400,
	"error": "Bad Request",
	"errors": [
		{
			"codes": [
				"NotBlank.user.username",
				"NotBlank.username",
				"NotBlank.java.lang.String",
				"NotBlank"
			],
			"arguments": [
				{
					"codes": [
						"user.username",
						"username"
					],
					"arguments": null,
					"defaultMessage": "username",
					"code": "username"
				}
			],
			"defaultMessage": "用户名不能为空",
			"objectName": "user",
			"field": "username",
			"rejectedValue": null,
			"bindingFailure": false,
			"code": "NotBlank"
		}
	],
	"message": "Validation failed for object='user'. Error count: 1",
	"path": "/users"
}

这个结果我们能接受吗? 不应该返回一个简单的提示吗? 返回这一大串不是我们想要的。所以是需要进行额外的处理

短路校验

很多时候我们要校验多个字段时,并不需要一个一个全部校验完毕了再返回错误信息,而是一单发现有一个入参不对,就立马返回错误,不再对剩余的参数进行相关校验,我们需要进行相关配置:

@Configuration
public class ValidConfig {
 
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 快速失败模式
                .failFast(true)
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }
}

统一异常处理

校验失败后,默认的接口返回含有太多的信息,不符合我么统一包装的返回处理,需要进行处理。一般,校验失败产生 org.springframework.web.bind.MethodArgumentNotValidException 异常,需要对这个进行拦截处理

@RestControllerAdvice
public class ExceptionHandlerConfig {

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseEntity dealMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        String message = allErrors.stream().map(s -> s.getDefaultMessage()).collect(Collectors.joining(";"));
        final ResponseEntity<String> response = ResponseEntity.status(400).body(message);
        return response;
    }
}

BindingResult 使用

除了统一异常处理的方法外,我们也可以搭配 bindingResult 这个类一起使用

@PostMapping("/est")
public MyResponse test(@RequestBody @Valid TestEntity test,BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        retrun new MyResponse("错误提示码",bindingResult.getFieldError().getDefaultMessage());
     }
}

 @Validated 和 @Valid

上面的代码涉及了这2个注解,我们来学习一下:

@Validated 是 Spring 框架提供的注解,用于标注在类、方法、参数上,但是不能用在字段上,提供了一个分组功能。

@Valid 是 Java 标准库(javax.validation)中的注解,用于标注在类的属性或方法参数上,它是 JSR-303(Bean Validation)的一部分,是一种基于注解的校验规范,可以进行级联和递归校验

有了上面的了解,在日常coding中我们要避免一些失效的场景:

1、参数如果是非对象格式,需要在controller类上面添加@Validated注解

springboot校验字符串长度_spring boot

2、参数如果是对象的话,属性的前面的需要添加 @Valid 或 @Validated 注解

springboot校验字符串长度_递归_02

3、如果是嵌套对象的话,里面的对象还要添加 @Valid注解

springboot校验字符串长度_springboot校验字符串长度_03

常见的一些参数校验注解主要有:

@Null   限制只能为null
 @NotNull    限制必须不为null
 @AssertFalse    限制必须为false
 @AssertTrue 限制必须为true
 @DecimalMax(value)  限制必须为一个不大于指定值的数字
 @DecimalMin(value)  限制必须为一个不小于指定值的数字
 @Digits(integer,fraction)   限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
 @Future 限制必须是一个将来的日期
 @Max(value) 限制必须为一个不大于指定值的数字
 @Min(value) 限制必须为一个不小于指定值的数字
 @Past   限制必须是一个过去的日期
 @Pattern(value) 限制必须符合指定的正则表达式
 @Size(max,min)  限制字符长度必须在min到max之间
 @Past   验证注解的元素值(日期类型)比当前时间早
 @NotEmpty   验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
 @NotBlank   验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty, 
 @NotBlank只应用于字符串且在比较时会去除字符串的空格
 @Email  验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
 @Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
 @Range(min=, max=,message=) 检查数字是否介于min和max之间.

@Validated的分组校验

同一个实体类在不同接口下可能都会做作为参数,而同一个字段在不同业务场景下的校验规则又可能不一样,这里就需要我们通过分组来解决这样的问题

分组接口

public interface TestGroup {
    interface Test1{

    }

    interface Test2{

    }
}

实体类的分组规则 

@Data
public class QueryDTO {
 
    @NotBlank.List({
            @NotBlank(message = "id不能为空", groups = {TestGroup.Test1.class}),
            @NotBlank(message = "id不能为空", groups = {TestGroup.Test2.class})
    })
    private Long id;
 
    @Max(value = 10, message = "age不能大于10", groups = {TestGroup.Test1.class})
    @Max(value = 30, message = "age不能大于30", groups = {TestGroup.Test2.class})
    private Integer age;
}

入参要指定分组

@PostMapping("/test2")
    public ResponseEntity test2(@Validated(value = {TestGroup.Test1.class}) @RequestBody QueryDTO queryDTO) {
        return ResponseEntity.ok().build();
    }