SpringBoot中的参数校验

SpringBoot中的参数校验

背景

为了保证数据的正确性, 避免埋坑, 参数校验在日常业务开发中用得非常多, 在Spring中用得最多的就是使用JSR303– Bean Validation规范提供的校验, Hibernate Validator是Bean Validation的参考实现, Hibernate Validator提供了JSR303规范中所有内置constraint的实现,除此之外还有一些附加的constraint。SpringBoot也提供了starter方便我们快速的支持参数校验.

Bean Validation中的常用注解

表1. Bean Validation中内置的constraint

表2. Hibernate Validator中附加的constraint

SpringBoot中的使用

引入starter

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

引入之后SpringBoot已经自动配置好了参数校验, 使用就变得非常简单了.

bean的校验

定义需要验证的bean

public class User {

    private Long id;

    @NotBlank(message = "姓名不能为空")
    private String name;

    @NotNull(message = "年龄不能为空")
    @Min(value = 1, message = "请输入合法年龄")
    private Integer age;

    ... Getter Setter toString省略
}

这里的message也可以写成国际化的key

编写controller模拟新增方法

    @PostMapping("/users")
    public ResponseEntity<String> addUser(@Valid @RequestBody User user) &#123;
        System.out.println(user.toString());
        return ResponseEntity.ok("validate success");
    &#125;

这里在需要验证的bean参数前加上@Valid注解, 加了该注解之后, 方法被请求时将会对bean中添加了注解的字段进行验证

定义全局异常处理器

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, String> handleValidationExceptions(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;
    }

这里对bean验证失败抛出的MethodArgumentNotValidException进行了处理, 方便前端进行展示

测试请求一下接口

post localhost:8080/users
{
    "name": "fantasy",
    "age": 0
}

返回结果:

{
    "age": "请输入合法年龄"
}

简单参数的校验

上面的参数类型是bean, 如果我们需要对诸如String类型这样简单的参数进行校验呢?

我们编写一个简单的查询方法, 按姓名查询, 需要对请求的参数进行校验

    @GetMapping("/v1/users")
    public ResponseEntity<String> getUsers(@NotBlank String name) &#123;
        System.out.println(name);
        return ResponseEntity.ok("validate success");
    &#125;

前面说过JSR303是对bean的校验, 不支持对普通参数进行校验, 但是spring提供了@Validated注解可以对方法参数进行校验, 但是@Validated需要添加在class上

@RestController
@Validated
public class UserController &#123;
    ...
&#125;

这样就可以对普通参数进行校验了, 但普通参数校验失败抛出的异常不再是上面的MethodArgumentNotValidException.class, 而是ConstraintViolationException, 所以我们还需要在上面的异常拦截器中处理ConstraintViolationException异常

    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, String> handle(ConstraintViolationException exs) &#123;
        Map<String, String> errors = new HashMap<>();
        exs.getConstraintViolations().forEach(err -> errors.put(err.getPropertyPath().toString(), err.getMessage()));
        return errors;
    &#125;

这里顺便说明一下@Validated@Valid的区别, 简单的说@Valid是JSR303的标准实现, 而@Validated是Spring提供的实现, 它们主要区别如下:

注解 范围 嵌套 校验组
@Valid 可以标记方法、构造函数、方法参数和成员属性(字段)上 支持 不支持
@Validated 可以标记类、方法、方法参数,不能用在成员属性(字段)上 不支持 支持

分组校验

当bean中的同一个字段需要不同校验规则时, 我们就需要分组校验

定义两个接口标识查询和更新

public interface QueryAction &#123;

&#125;
public interface UpdateAction &#123;

&#125;

对User类稍作修改, 添加groups属性

   @NotBlank(message = "姓名不能为空", groups = &#123;UpdateAction.class, QueryAction.class&#125;)
    private String name;

    @NotNull(message = "年龄不能为空", groups = UpdateAction.class)
    @Min(value = 1, message = "请输入合法年龄", groups = UpdateAction.class)
    private Integer age;

这时候, 只用在controller的方法中使用@Validated提供的分组校验功能即可, 修改我们第一个接口的入参为:

    @PostMapping("/users")
    public ResponseEntity<String> addUser(@Validated(value = UpdateAction.class) @RequestBody User user) &#123;
        System.out.println(user.toString());
        return ResponseEntity.ok("validate success");
    &#125;

指定这个方法使用更新操作的校验规则即可

嵌套校验

当一个bean中存在另一个bean属性时, 可以使用嵌套校验, 只用在在bean中加上@Valid注解即可

public class User &#123;
    @Valid
    @NotNull
    private Action action;
&#125;

自定义校验

在业务中还会有一些特殊场景, 需要对某些字段增加自定义的校验逻辑, 比如想校验地址是否以中国开头, 这时候需要自定义注解, 并且实现ConstraintValidator接口自定义校验逻辑

@Documented
@Constraint(validatedBy = StartWithValidator.class)
@Target(&#123;METHOD, FIELD&#125;)
@Retention(RUNTIME)
public @interface StartWithValidation &#123;

    String message() default "不符合要求的初始值";

    String start() default "";

    Class<?>[] groups() default &#123;&#125;;

    Class<? extends Payload>[] payload() default &#123;&#125;;

&#125;

public class StartWithValidator implements ConstraintValidator<StartWithValidation, String> &#123;

    private String start;

    @Override
    public void initialize(StartWithValidation constraintAnnotation) &#123;
        start = constraintAnnotation.start();
    &#125;

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) &#123;
        if (!StringUtils.isEmpty(s)) &#123;
            return s.startsWith(start);
        &#125;
        return false;
    &#125;
&#125;

这时候只需要在需要验证的字段上加上我们自定义的@StartWithValidation即可

总结

本篇文章到此over, 代码在此.


   转载规则

本文不允许转载。
 上一篇
SSM重构到SpringBoot导致@Scheduled失效的坑 SSM重构到SpringBoot导致@Scheduled失效的坑
SSM重构到SpringBoot定时任务失效的坑 最近重构老项目, 把SSM的祖传代码升级成SpringBoot, 最后发现原先代码中使用了@Scheduled注解的定时任务不再执行. 背景SSM项目中使用spring-task需要在xm
下一篇 
Spring中事务你用对了吗 Spring中事务你用对了吗
Spring中事务你用对了吗 背景 Spring中为JTA,JPA,Hibernate等事务API提供了一致性的编程模型,但是编程式事务需要编码支持,在实际中很少使用。所以Spring提供了声明式事务,配合SpringBoot,我们可以通
2020-04-09