参数校验

基本用法

Spring Boot Validation 提供了一系列注解,用于在实体类中定义验证规则。以下是一些常用的校验相关的注解及其功能以及用法:

1.@NotNull 校验元素值不能为 null。如果元素为null,则验证失败。通常用于字段级别的验证。

2.@NotBlank 校验字符串元素值不能为 null 或空字符串。必须包含至少一个非空格字符(即执行trim()之后不为’')。如果元素为null或者‘‘,则验证失败。通常用于String类型的字段校验。

3.NotEmpty 校验集合元素或数组元素或者字符串是否非空。通常作用于集合字段或数组字段,此时需要集合或者数字的元素个数大于0。也可以作用于字符串,此时校验字符串不能为null或空串(可以是一个空格)。注意与@NotBlank的使用区别。

4.@Length 校验字符串元素的长度。作用于字符串。

5.@Size 校验集合元素个数或字符串的长度在指定范围内。在集合或字符串字段上添加 @Size 注解。

@Size(min = 1, max = 10, message = "Number of items must be between 1 and 10")
private List<String> items;

@Size(min = 5, max = 20, message = "Length must be between 5 and 20 characters")
private String username;

6.@Min 校验数字元素的最小值。

7.@Max 校验数字元素的最大值。

9.@DecimalMax 作用于BigDecimal类型字段, 校验字段的最大值,支持比较的值为字符串表示的十进制数。通常搭配它的inclusive()使用,区别边界问题。value 属性表示最大值,inclusive 属性表示是否包含最大值。

@DecimalMax(value = "100.00", inclusive = true, message = "Value must be less than or equal to 100.00")
private BigDecimal amount;

10.@DecimalMin 作用于BigDecimal类型字段, 校验字段的最小值,支持比较的值为字符串表示的十进制数。通常搭配它的inclusive()使用,区别边界问题。value 属性表示最小值,inclusive 属性表示是否包含最小值。

11.@Email 校验字符串元素是否为有效的电子邮件地址。可以通过regexp自定义邮箱匹配正则。

12.@Pattern 根据正则表达式校验字符串元素的格式。

@Pattern(regexp = "[a-zA-Z0-9]+", message = "Only alphanumeric characters are allowed")
private String username;

13.@Digits 校验数字元素的整数部分和小数部分的位数。作用于BigDecimalBigInteger,字符串,以及byte, short,int, long以及它们的包装类型。

@Digits(integer = 5, fraction = 2, message = "Number must have up to 5 integer digits and 2 fraction digits")
private BigDecimal amount;

14.@Past 校验日期或时间元素是否在当前时间之前。即是否是过去时间。作用于Date相关类型的字段。

15.@Future 校验日期或时间元素是否在当前时间之后。即是否是未来时间。作用于Date相关类型的字段。

16.@AssertTrue:必须是true

17.@AssertFalse:必须是false

------ 2024/05/18 更新:

  1. @Valid:注解校验提交的ListObject等,进行方法级验证,以及用于标记成员属性以进行验证。不过,该注解不支持分组验证。
@Data
public class UserAccount {

@NotNull
@Size(min = 4, max = 15)
private String password;

@NotBlank
private String name;
}
@RequestMapping(value = "/saveBasicInfo", method = RequestMethod.POST)
public String saveBasicInfo(
@Valid @ModelAttribute("useraccount") UserAccount useraccount,
BindingResult result,
ModelMap model) {
if (result.hasErrors()) {
return "error";
}
return "success";
}

当然也可以在pojo层用

public class UserAddress {

@NotBlank
private String countryCode;

// 其他代码省略
}
public class UserAccount {

//...

@Valid
@NotNull(groups = AdvanceInfo.class)
private UserAddress useraddress;

// 其他代码省略
}
  1. @Validated:对于分组级(Group-Level)验证,必须使用 Spring 的 @Validated,它是 JSR-303 的 @Valid 的变体,用于方法级。

有时我们的入参是基本类型,例如使用@RequestParam@PathVariable 标记的参数

@Validated
@RestController
@RequestMapping("/validation")
public class ValidateController {
...
@GetMapping("/boy-friends")
public ResponseEntity<BoyFriend> updateBoyFriend(@NotBlank @RequestParam("name") String name) {
...
}
}

我们只需要给参数添加上相应的约束注解,例如@NotBlank,然后再给Controller类添加上@Validated即可。

分组验证

有时一个类被多个方法使用,而每个方法对入参的要求却不一样,这种情况怎么办呢?例如我们的BoyFriend,里面有一个体重的属性,创建时要求不能高于85kg,由于条件苛刻,于是修改的时候要求不能高于100kg。

Spring提供了一种解决方法,那就是使用分组。每个注解里面都可以设置其属于哪些分组,在验证的时候只验证属于自己分组的那些约束。

例如我们这里设置两个分组:创建和更新,当调用创建方法的时候就只验证属于创建分组的约束,不高于85kg…

public class BoyFriend {
@Max(groups = BoyFriendCreate.class, value = 85)
@Max(groups = BoyFriendUpdate.class, value = 100)
private Integer weight;
}
  • 定义分组

分组必须是接口,例如我们这里定义了两个分组

public interface BoyFriendCreate {
}

public interface BoyFriendUpdate {
}
  • 给Controller方法添加分组

先给Controller类添加@Validated,然后给方法添加带有分组信息的@Validated

@Validated(BoyFriendUpdate.class)
@PatchMapping("/boy-friends")
public ResponseEntity<BoyFriend> updateBoyFriend(@Valid @RequestBody BoyFriend boy) {
return ResponseEntity.ok(boy);
}

全局异常处理

每个Controller方法中如果都写一遍BindingResult信息的处理还是很繁的。当我们写了@validated注解,不写BindingResult的时候,Spring 就会抛出异常。因此,我们可以通过全局异常处理的方式统一处理校验异常,从而免去重复编写异常信息的代码。全局异常处理类只需要在类上标注@RestControllerAdvice,并在处理相应异常的方法上使用@ExceptionHandler注解,写明处理哪个异常即可。

全局异常处理类 GlobalExceptionHandler

@RestControllerAdvice
public class GlobalExceptionHandler {
private static final String VALID_FAIL_MSG = "参数检验不通过";

//处理 form data方式调用接口校验失败抛出的异常
@ExceptionHandler(BindException.class)
public Result<List<String>> bindExceptionHandler(BindException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> collect = fieldErrors.stream().map(o -> o.getDefaultMessage()).toList();
return Result.error(VALID_FAIL_MSG,collect);
}

// 处理 json 请求体调用接口校验失败抛出的异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<List<String>> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> collect = fieldErrors.stream().map(o -> o.getDefaultMessage()).collect(Collectors.toList());
return Result.error(VALID_FAIL_MSG,collect);
}

// 处理单个参数校验失败抛出的异常
@ExceptionHandler(ConstraintViolationException.class)
public Result<List<String>> constraintViolationExceptionHandler(ConstraintViolationException e) {
Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
List<String> collect = constraintViolations.stream().map(o -> o.getMessage()).collect(Collectors.toList());
return Result.error(VALID_FAIL_MSG,collect);
}

// 处理以上处理不了的其他异常
@ExceptionHandler(Exception.class)
public Result handleException(Exception e){
e.printStackTrace();
return Result.error(StringUtils.hasLength(e.getMessage())? e.getMessage() : "操作失败");
}
}

参考链接

@Valid 注解校验提交的List(list 集合) javax.validation.Valid_validlist-CSDN博客

秒懂SpringBoot之参数验证全解析(@Validated与@Valid) - 知乎 (zhihu.com)

Spring 中 @Valid 和 @Validated 注解的区别 - spring 中文网 (springdoc.cn)