springboot-请求入参验证

springboot-请求入参验证

  • 1. JSR-303验证规则
  • 2.使用@Validated验证入参
    • 方式一:手动处理验证结果
    • 方式二:用异常Handler处理验证结果并直接响应(推荐)
  • 3.自定义验证规则
    • 3.1 自定义验证注解@WorkOverTime
    • 3.2 自定义验证器WorkOverTimeValidator
  • 4.统一处理验证失败结果

Spring Boot 支持 JSR-303 、 Bean 验证框架 , 默认实现用的是 Hibernate validator。在Spring MVC 中,只需要使用@Valid 注解标注在方法参数上, Spring Boot 即可对参数对象进行校验 ,校验结果放在BindingResult 对象中。

1. JSR-303验证规则

JSR-303 是 Java 标准的验证框架 , 己有的实现有 Hibernate validator . JSR-303 定义了一系列注解用来验证 Bean 的属性,常用的有如下几种。

空检查

  • @Null , 验证对象是否为空 ;
  • @NotNull , 验证对象不为空 ;
  • @NotBlank , 验证字符串不为空或者不是空字符串, 比如””和””都会验证失败;
  • @NotEmpty,验证对象不为null ,或者集合不为空;

长度检查

  • @Size(min=, max=),验证对象长度,可支持字符串、集合;
  • @Length , 字符串大小;

数值检测

  • @Min ,验证数字是否大于等于指定的值;
  • @Max ,验证数字是否小于等于指定的值;
  • @Digits , 验证数字是否符合指定格式,如@Digits(integer=9,台action=2);
  • @Range , 验证数字是否在指定的范围内,如@Range(min=1,max=100);

其他

  • @Email , 验证是否为邮件格式,为 null 则不做校验;
  • @Pattern, 验证 String 对象是否符合正则表达式的规则。

以下是一个包含了验证注解的 JavaBean:

import lombok.Data;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Size;

/**
 * @Author ccl
 * @Date 2021/1/3 14:55
 */
@Data
public class User {

    @Null(groups = {Update.class},message = "id必须为空")//更新的时候必须为null
    @NotNull(groups = {Add.class},message = "id不能为空")//新增的时候必须不为null
    private Long id;
    @Size(min = 3, max = 20,message = "name长度必须在3到20之间")
    private String name;
    @Email(message = "email地址不合法")
    private String email;

    //定义一个group接口,更新时校验纽
    public interface Update {
    }

    //定义另一个group接口,添加时校验纽
    public interface Add {
    }
}

通常,不同的业务逻辑会有不同的验证逻辑,比如对于 User来说,当更新的时候,id 必须不为 null ,但增加的时候, id 必须是 null 。

JSR-303 定义了 group 概念,每个校验注解都必须支持。校验注解作用在宇段上的时候,可以指定一个或者多个 group , 当 Spring Boot 校验对象的时候,也可以指定校验的上下文属于哪个 group。这样 , 只有 group 匹配的时候,校验注解才能生效。

2.使用@Validated验证入参

在 Controller 中 , 只需要给方法参数加上@Validated 即可触发一次校验 。

参数使用了@Validated 注解,将触发 Spring 的校验 , 井将验证结果存放到 BindingResult 对象中 。 这里, Validated 注解使用了校验的上下文 User.Add.class , 因 此, 整个校验将按照 Add.class 来校验。

BindingResult 包含了验证结果,提供了如下方法 :

  • hasErrors ,判断验证是否通过 :
  • getAllErrors ,得到所有的错误信息,通常返回的是 FieldError 列表。

方式一:手动处理验证结果

如果 Controller 参数未提供 BindingResult 对象, 则 SpringMVC 将抛出异常。

import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 1. 参数验证demo
 2.  3. @Author ccl
 3. @Date 2021/1/3 14:54
 */
@RestController
public class ValidateDemoController {

    @PostMapping("add")
    public Object add(@RequestBody @Validated({User.Add.class}) User user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            List<ObjectError> list = bindingResult.getAllErrors();
            FieldError error = (FieldError) list.get(0);
            return error.getDefaultMessage();
        }
        return "success";
    }

    @PostMapping("update")
    public Object update(@RequestBody @Validated({User.Update.class}) User user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            List<ObjectError> list = bindingResult.getAllErrors();
            FieldError error = (FieldError) list.get(0);
            return error.getDefaultMessage();
        }
        return "success";
    }
}

方式二:用异常Handler处理验证结果并直接响应(推荐)

controller写法:

@PostMapping("update")
    public Object update(@RequestBody @Validated({User.Update.class}) User user) {
        return "success";
    }

异常Handler处理:

@RestControllerAdvice
@Slf4j
public class MyExceptionHandler {
    /**
     * 处理@Validated参数校验失败异常
     * @param exception 异常类
     * @return 响应
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<String> exceptionHandler(MethodArgumentNotValidException exception){
        BindingResult result = exception.getBindingResult();
        StringBuilder stringBuilder = new StringBuilder();
        if (result.hasErrors()) {
            List<ObjectError> errors = result.getAllErrors();
            if (errors != null) {
                errors.forEach(p -> {
                    FieldError fieldError = (FieldError) p;
                    log.warn("Bad Request Parameters: dto entity [{}],field [{}],message [{}]",fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage());
                    stringBuilder.append(fieldError.getDefaultMessage());
                });
            }
        }
        return Result.fail(stringBuilder.toString());
    }
 }

3.自定义验证规则

JSR-303 提供的大部分校验注解已经够用,也允许定制校验注解,比如在 User类中,我们新增一个加班时间:

@WorkOverTime(max=2)
int workTime ;

属性 workTime 使用了注解@WorkOverTime,当属性值超过 max 值的时候,将会验证失败 。

3.1 自定义验证注解@WorkOverTime

WorkOverTime 跟其他注解差不多,但提供了@Constraint 来说明用什么类作为验证注解实现类,
代码如下 :

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Constraint(validatedBy = {WorkOverTimeValidator.class})
@Documented
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WorkOverTime {
    String message() default "加班时间过长,不能超过{max}小时";

    int max() default 5;

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

@Constraint 注解声明用什么类来实现验证 ,我们将创建一个 WorkOverTimeValidator类来进行验证。验证注解必须要提供如下信息:

  • message , 用于创建错误信息,支持表达式 , 如“错误 , 不能超过(max )小时 ”。
  • groups ,验证规则 分组, 比如新增 和修改的验证规则 不一样,分为两个组 , 验证注解必须提供。
  • payload , 定义了验证 的有效负荷。

3.2 自定义验证器WorkOverTimeValidator

WorkOverTimeValidator 必须实现 ConstraintValidator 接口 initialize 方法及验证方法 isValid :

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * 自定义验证器
 *
 * @Author ccl
 * @Date 2021/1/3 15:47
 */
public class WorkOverTimeValidator implements ConstraintValidator<WorkOverTime, Integer> {
    WorkOverTime work;
    int max;

    @Override
    public void initialize(WorkOverTime work) {
        //获取注解的定义
        this.work = work;
        max = work.max();
    }

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        //校验逻辑
        if (value == null) {
            return true;
        }
        return value < max;
    }
}

4.统一处理验证失败结果

把验证方法写到父类中,所有需要验证的controller都集成该父类。

import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;

/**
 * controller父类
 */
public class BaseController {
    /**
     * 验证入参,有验证失败的则抛出异常
     *
     * @param br
     * @throws BindException
     */
    public void validate(BindingResult br) throws BindException {
        if (br.hasErrors()) {
            throw new BindException(br);
        }
    }
}

controller改造如下:

import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * 参数验证demo
 *
 * @Author ccl
 * @Date 2021/1/3 14:54
 */
@RestController
public class ValidateDemoController extends BaseController {

    @PostMapping("add")
    public Object add(@RequestBody @Validated({User.Add.class}) User user, BindingResult bindingResult) throws BindException {
        this.validate(bindingResult);
        return "success";
    }

    @PostMapping("update")
    public Object update(@RequestBody @Validated({User.Update.class}) User user, BindingResult bindingResult) throws BindException {
        this.validate(bindingResult);
        return "success";
    }
}

直接抛出BindException 异常,
再使用之前讲的《Spring 全局异常处理三种方式》进行异常处理即可。

如果觉得本文对您有帮助,请点赞支持,非常感谢;

你可能感兴趣的:(springboot,java,spring,spring,boot)