Java SpringBoot 参数校验

一、SpringBoot之参数校验

1、SpringBoot提供的参数校验注解

除了@NotEmpty@NotBlank将 null 值认为是非法的之外,其它注解如@Size, @Max, @Min等都将 null 认为是有效的,如果不允许 null 值,则需要额外添加@NotNull注解。

注解 说明
**@NotEmpty** 字符串、集合、Map、数组等不能为 null 或空
**@NotBlank** 字符串不能为 null,且至少包含一个非空字符
**@NotNull** 任意类型 不能为 null
**@Size** 字符串、集合、Map、数组等元素的个数必须在指定的 min 和 max 范围内
**@Email** 字符串是有效的邮箱
**@Digits** 字符串、整数、浮点数是一个数字,参数 integer 表示整数位的数字个数,fraction 表示小数位的数字个数
**@CreditCardNumber** 字符串是有效的信用卡数字,不校验信用卡本身的有效性
**@AssertTrue** 布尔类型必须是 true,null 值被认为是有效的
**@Max** 整数、浮点数必须小于等于指定的最大值
**@Min** 整数、浮点数必须大于等于指定的最小值
**@Range** 字符串、数字必须在指定的 min 和 max 范围内
**@Pattern** 字符串必须匹配指定的正则表达式
  1. @Data
  2. public class Employee implements Serializable {
  3. private static final long serialVersionUID = -8224860450904540019L;
  4. @NotEmpty(message = "名字不能为空")
  5. @UTF8Size(max = 16, message = "name should be short than 128")
  6. private String name;
  7. @Email
  8. private String email;
  9. @NotBlank(message = "city is required")
  10. @Size(max = 128, message = "city should be short than 128")
  11. private String city;
  12. @CreditCardNumber(message = "invalid credit card number")
  13. private String ccNumber;
  14. @Pattern(regexp = "^(0[1-9]|1[0-2])([\\\\/])([1-9][0-9])$", message = "required format MM/YY")
  15. private String ccExpiration;
  16. @Digits(integer = 3, fraction = 0, message = "invalid CVV")
  17. private String ccCVV;
  18. }

2、自定义校验注解

@Size并不能支持中文字符,可以自定义如下然后在需要校验字符(中英文)的字段上使用@UTF8Size即可。

  1. @Documented
  2. @Constraint(validatedBy = Utf8SizeValidator.class)
  3. @Target({METHOD, FIELD})
  4. @Retention(RetentionPolicy.RUNTIME)
  5. public @interface UTF8Size {
  6. String message() default "{javax.validation.constraints.Size.message}";
  7. int min() default 0;
  8. int max() default Integer.MAX_VALUE;
  9. Class<?>[] groups() default {};
  10. Class<? extends Payload>[] payload() default {};
  11. }
  1. public class Utf8SizeValidator implements ConstraintValidator<UTF8Size, String> {
  2. private int maxCharSize;
  3. @Override
  4. public void initialize(UTF8Size constraintAnnotation) {
  5. this.maxCharSize = constraintAnnotation.max();
  6. }
  7. @Override
  8. public boolean isValid(String value, ConstraintValidatorContext context) {
  9. if (Objects.isNull(value)) {
  10. return true;
  11. }
  12. return value.getBytes(Charset.forName("GB18030")).length <= maxCharSize;
  13. }
  14. }

二、字段校验的使用

使用 Bean Validation API 的@Valid注解,或者 Spring Context 提供的@Validated注解启用对 Bean 的校验。主要区别是,@Validated@Valid的变体,支持分组校验(validation groups)。

A.这种写法会将异常信息抛给全局异常处理

  1. @PostMapping("/emp")
  2. public String addEmploy(@RequestBody @Valid Employee employee) {
  3. log.info("employee to create: {}", employee);
  4. String employeeId = UUID.randomUUID().toString();
  5. return employeeId;
  6. }

B.如果需要捕获参数校验的异常结果,写法如下

:::danger 这种写法不会将异常处理的结果返回给全局异常处理 :::

  1. @PostMapping()
  2. UserInfo addUser(@RequestBody @Valid UserInfo userInfo, BindingResult bindingResult) {
  3. userService.addUser(userInfo);
  4. return userInfo;
  5. }

三、字段校验的全局异常处理

  1. package org.hand.train.springboot.springboot.exception;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.http.HttpStatus;
  4. import org.springframework.validation.BindingResult;
  5. import org.springframework.validation.FieldError;
  6. import org.springframework.web.bind.MethodArgumentNotValidException;
  7. import org.springframework.web.bind.annotation.ControllerAdvice;
  8. import org.springframework.web.bind.annotation.ExceptionHandler;
  9. import org.springframework.web.bind.annotation.ResponseBody;
  10. import org.springframework.web.bind.annotation.ResponseStatus;
  11. import java.util.stream.Collectors;
  12. /**
  13. * GlobalExceptionHandler
  14. * encoding:UTF-8
  15. *
  16. * @author Fcant
  17. * @date 10:02 2019/12/4
  18. */
  19. @Slf4j
  20. @ControllerAdvice
  21. public class GlobalExceptionHandler {
  22. @ExceptionHandler(MethodArgumentNotValidException.class)
  23. @ResponseBody
  24. @ResponseStatus(HttpStatus.BAD_REQUEST)
  25. public String handleMethodArgumentNotValidException(
  26. MethodArgumentNotValidException exception) {
  27. log.error("method argument not valid: {}", exception.getMessage());
  28. String errorMessage = "field error";
  29. BindingResult bindingResult = exception.getBindingResult();
  30. if (bindingResult.hasErrors()) {
  31. errorMessage = bindingResult.getFieldErrors().stream()
  32. .map(FieldError::getDefaultMessage)
  33. .collect(Collectors.joining(" | "));
  34. }
  35. return errorMessage;
  36. }
  37. }

四、分组校验

相同字段在不同场景需要不同的验证策略

A.组接口

  1. public class GroupVaildDTO {
  2. public interface SaveGroup extends Default {}
  3. public interface UpdateGroup extends Default {}
  4. }

B.校验的JavaBean属性

  1. public class GroupsValidForm {
  2. @Null(message = "id必须为null", groups = {GroupVaildDTO.SaveGroup.class})
  3. @NotNull(message = "id不能为null", groups = {GroupVaildDTO.UpdateGroup.class})
  4. private Integer id;
  5. @NotBlank(message = "用户名不能为空")
  6. private String userName;
  7. @Override
  8. public String toString() {
  9. final StringBuffer sb = new StringBuffer("GroupsValidForm{");
  10. sb.append("id=").append(id);
  11. sb.append(", userName='").append(userName).append('\'');
  12. sb.append('}');
  13. return sb.toString();
  14. }
  15. }

C.Controller层分组校验的使用

相同字段在不同场景需要不同的验证策略

  1. @PostMapping(value = "update")
  2. public ServerResponse update(@RequestBody @Validated(value = GroupVaildDTO.UpdateGroup.class)
  3. GroupsValidForm groupsValidForm, BindingResult results) {
  4. if (results.hasErrors()) {
  5. return ServerResponse.createByErrorMessage(results.getFieldError().getDefaultMessage());
  6. }
  7. return ServerResponse.createBySuccess();
  8. }