Spring Boot下如何校验Spring MVC的请求参数及如何自定义校验注解

开发 后端
Spring Boot提供了spring-boot-starter-validation 为Bean的校验提供支持。我们可以通过一系列的校验注解对Java Bean的属性进行校验。

在Java世界里,用于Java Bean的校验的标准规范是JSR 380,又被称作Bean Validation 2.0。而JSR 380的实现是Hibernate Validator。

Spring Boot提供了spring-boot-starter-validation 为Bean的校验提供支持。我们可以通过一系列的校验注解对Java Bean的属性进行校验。

本文将演示如何在对Spring MVC的请求参数进行校验。当然它可以用在任何Java Bean的校验上。

我们先新建一个演示项目,注意除了添加“Spring Web”依赖,还需要额外添加“Validation”依赖,从Spring Boot 2.3后,我们需要显示添加此依赖。

常用JSR 380注解及如何校验请求DTO

@Data
public class PersonDto {
    @NotNull(message = "姓名不能为空")
    private String name;
    @Min(value = 18, message = "年纪最小18")
    @Max(value = 60, message = "年纪最大60")
    private Integer age;
    @Email(message = "地址只能邮件")
    private String email;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

在DTO这个JavaBean上使用JSR 380注解,主要的注解有:

  • @NotNull :校验注解的属性不能为null
  • @AssertTrue : 校验注解的属性是true
  • @Size : 校验大小范围是在min和max之间,可以是String、Collection、Map,和数组属性
  • @Min: 校验注解的属性最小不能小于设定的值
  • @Max: 校验注解的属性最大不能大于设定的值
  • @Pattern:校验当前字符串属性符合指定的正则表达式
  • @Email :校验注解属性是一个有效的邮件地址
  • @NotEmpty:校验注解的属性不能为null或者是empty,以是String、Collection、Map,和数组属性
  • @NotBlank: 校验文本属性不为null或者空格
  • @Positive 和 @PositiveOrZero :校验整数是正数/正数或0
  • @Negative 和 @NegativeOrZero :校验整数是负数/负数或0
  • @Past and @PastOrPresent :校验日期是过去/过去或现在
  • @Future and @FutureOrPresent :校验日期是未来/未来或现在

注意上面的“message”中设置提示错误的信息。

控制器如何生效及如何校验请求参数

@RestController
@RequestMapping("/people")
@Validated //2
public class PersonController {

    @PostMapping
    public String save(@Valid @RequestBody PersonDto personDto){ //1
        return "OK";
    }

    @GetMapping("/findByAge") //2
    public String findByAge(@Range(min = 18,max = 60, message = "年纪只能是18到60之间") Integer age){
        return "OK";
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

1、校验DTO,只需要在@RequestBody前加上注解“@Valid”,若出现校验错误,抛出MethodArgumentNotValidException

2、检验请求中的参数(包括路径变量),我们需要在类上注解“@Validated”才会生效,若出现校验错误,抛出ConstraintViolationException。此处的@Range注解来自于Hibernate Validator,非标准JSR 380注解。

全局异常处理,给前端友好的校验提示

当出现校验错误时,Spring Boot只会抛出异常,所以为了让前端更友好的处理这些异常,我们将对这些抛出的异常做出处理。

@RestControllerAdvice
public class CustomExceptionHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, String> validationExceptionHandler(MethodArgumentNotValidException  ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return errors;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    public String validationExceptionHandler(ConstraintViolationException  ex) {

        return ex.getMessage();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

验证效果

运行应用,并违反校验规则,如图:

如何自定义检验注解

上面我们使用了JSR 380以及Hibernate的注解来校验,这节我们演示如何自定义校验注解。

我们通过定义个校验注解“@Enumeration”和在“EnumerationValidator”类中定义校验规则。

当前例子演示功能为:只能是枚举里允许的值,否则校验不通过。

  • 注解定义

@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumerationValidator.class) //3
public @interface Enumeration {
    String message() default "属性只能从列表中来"; //1
    String[] values(); //2
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

1、message属性定义错误信息

2、values设置可选的枚举值

3、通过“@Constraint”指定校验的类

注意:若没有groups()和payload(),会报:javax.validation.constraintdefinitionexception: hv000074

  • 校验逻辑类

public class EnumerationValidator implements ConstraintValidator<Enumeration,String> {
    private String message;
    private List<String> allowable;
    @Override
    public void initialize(Enumeration enumeration){
        this.message = enumeration.message();
        this.allowable = Arrays.asList(enumeration.values());
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context){
        return this.allowable.contains(value);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

这里的逻辑很简单,只需要可选的枚举值包含被校验的值即可。

  • 控制器中,当然也配合了类上注解的“@Validated”

@GetMapping("/findByGender")
    public String findByGender(@Enumeration(values = {"男","女"}, message = "性别只能是男或女") String gender){
        return "OK";
    }
  • 1.
  • 2.
  • 3.
  • 4.

启动验证结果:

感谢对我的书《从企业级开发到云原生微服务:Spring Boot实战》的支持。

参考资料:

https://medium.com/codex/spring-boot-create-custom-annotation-to-validate-request-parameter-dcf483539d90

https://www.baeldung.com/spring-boot-bean-validation

https://reflectoring.io/bean-validation-with-spring-boot/

文章出自:​​爱科学的卫斯理​​,如有转载本文请联系爱科学的卫斯理今日头条号。

责任编辑:武晓燕 来源: 今日头条
点赞
收藏

51CTO技术栈公众号