Springboot中参数校验优雅实践

在提供对外的API接口的时候,我们经常需要对参数进行校验。通常的做法是在程序中通过if 进行判断,例如:

package com.validator.demo.api.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.validator.demo.api.base.DateResult;
import com.validator.demo.api.base.ResultCode;
import com.validator.demo.api.model.dto.PeopleDTO;
import com.validator.demo.api.model.vo.PeopleVO;

@RestController
@RequestMapping("/test/api")
public class ValidatorController {
    @PostMapping("/validatortest")
    public DateResult<PeopleVO> test(@RequestBody PeopleDTO peopleDTO) {
        DateResult<PeopleVO> dateResult = new DateResult<PeopleVO>();
        String name = peopleDTO.getName();
        String address = peopleDTO.getAddress();
        if (name == null) {
            dateResult.setCode(ResultCode.FAIL.code());
            dateResult.setMsg("姓名不可以为空");
        }
        if (address != null) {
            if (address.length() <= 6) {
                dateResult.setCode(ResultCode.FAIL.code());
                dateResult.setMsg("地址长度不正确");
            }
        }
        //具体的业务逻辑
        //省略
        return dateResult;
    }

}

我们输入相应的参数进行验证,返回如下:

如果参数比较多我们就需要写更多的if进行判断(假如有10个参数需要进行非空校验,那么我们就需要写10个if),并且参数的校验逻辑与业务代码混在一起不利于后期的维护。接下来我们看看如何使用Hibernate Validator对参数进行优雅校验。包含的注解如下:

Bean Validation 中内置的 constraint

@Valid

被注释的元素是一个对象,需要检查此对象的所有字段值

@Null

被注释的元素必须为 null

@NotNull

被注释的元素必须不为 null

@AssertTrue

被注释的元素必须为 true

@AssertFalse

被注释的元素必须为 false

@Min(value)

被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@Max(value)

被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@DecimalMin(value)

被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@DecimalMax(value)

被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@Size(max, min)

被注释的元素的大小必须在指定的范围内

@Digits (integer, fraction)

被注释的元素必须是一个数字,其值必须在可接受的范围内

@Past

被注释的元素必须是一个过去的日期

@Future

被注释的元素必须是一个将来的日期

@Pattern(value)

被注释的元素必须符合指定的正则表达式

 

Hibernate Validator 附加的 constraint

注解

作用

@Email

被注释的元素必须是电子邮箱地址

@Length(min=, max=)

被注释的字符串的大小必须在指定的范围内

@NotEmpty

被注释的字符串的必须非空

@Range(min=, max=)

被注释的元素必须在合适的范围内

 

@NotBlank

被注释的字符串的必须非空

@URL(protocol=,
host=,    port=, 
regexp=, flags=)

被注释的字符串必须是一个有效的url

@CreditCardNumber

被注释的字符串必须通过Luhn校验算法,
银行卡,信用卡等号码一般都用Luhn
计算合法性

@ScriptAssert
(lang=, script=, alias=)

要有Java Scripting API 即JSR 223 
("Scripting for the JavaTM Platform")的实现

@SafeHtml
(whitelistType=, 
additionalTags=)

classpath中要有jsoup包

springboot中spring-boot-starter-web包里面有hibernate-validator包,所以不需要单独引用hibernate validator依赖。

首先我们先添加一个DTO,在DTO的字段上面添加相应的注解

package com.validator.demo.api.model.dto;

import java.io.Serializable;

import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.NotBlank;

public class PeopleDTO implements Serializable {

    private static final long serialVersionUID = 7515422823626784776L;
    @NotBlank(message = "姓名不能为空")
    private String            name;
    @NotBlank(message = "性别不能为空")
    private String            sex;
    @NotBlank(message = "生日不能为空")
    @Pattern(regexp = "[0-9]{4}-[0-9]{2}-[0-9]{2}", message = "生日输入数据异常,请确认!")
    private String            birthday;
    @NotBlank(message = "地址不能为空!")
    @Size(min = 6, message = "地址不能小于六个字符,请确认!")
    private String            address;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getBirthday() {
        return birthday;
    }

    public void setBirthday(String birthday) {
        this.birthday = birthday;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

}

在Controller中验证请求参数时,在@RequestBody加注解 @Valid然后后面加BindindResult即可

package com.validator.demo.api.controller;

import javax.validation.Valid;

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

import com.validator.demo.api.base.DateResult;
import com.validator.demo.api.base.ResultCode;
import com.validator.demo.api.model.dto.PeopleDTO;
import com.validator.demo.api.model.vo.PeopleVO;

@RestController
@RequestMapping("/test/api")
public class ValidatorController {
    @PostMapping("/validatortest")
    public DateResult<PeopleVO> test(@RequestBody @Valid PeopleDTO peopleDTO, BindingResult result) {
        DateResult<PeopleVO> dateResult = new DateResult<PeopleVO>();
        String msg = "";
        if (result.hasErrors()) {
            dateResult.setCode(ResultCode.FAIL.code());
            for (ObjectError error : result.getAllErrors()) {
                msg += error.getDefaultMessage() + ";";
            }
        }
        //具体的业务逻辑
        //省略
        dateResult.setMsg(msg);
        return dateResult;
    }

}

在postman中请求返回如下:

从上面的错误信息中我们可以看到返回了所有的错误信息,在实际项目中可能有这样的需求,只要一个参数有问题就返回相应的错误。Hibernate Validator有以下两种验证模式:

1、普通模式(默认是这个模式)
  普通模式(会校验完所有的属性,然后返回所有的验证失败信息)
返回目录
2、快速失败返回模式
  快速失败返回模式(只要有一个验证失败,则返回)

我们创建一个Configuration,把验证模式修改为快速失败模式

package com.validator.demo.api.config;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ValidatorConfiguration {
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure()
                .addProperty("hibernate.validator.fail_fast", "true").buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();

        return validator;
    }
}

然后从新请求返回如下,修改验证模式后只返回了一个错误

上面的controller控制层我们使用了BindingResult对错误信息进行接收。@Valid 和 BindingResult 是一 一对应的,如果有多个@Valid,那么每个@Valid后面都需要添加BindingResult用于接收bean中的校验信息。下面我们定义一个全局异常用于接收校验信息,这样就不用再添加BindingResult用于接收bean中的校验信息。

新建一个全局异常,如下:

package com.validator.demo.api.exception;

import java.util.Iterator;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;

import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.validator.demo.api.base.Result;
import com.validator.demo.api.base.ResultCode;

@RestControllerAdvice
public class BaseException {
    @ExceptionHandler({ MethodArgumentNotValidException.class })
    public Result handleMAException(MethodArgumentNotValidException e) {
        Result result = new Result();
        String validatorMessage = "";
        BindingResult bindingResult = e.getBindingResult();
        if (bindingResult.hasErrors()) {
            for (ObjectError error : bindingResult.getAllErrors()) {
                validatorMessage = error.getDefaultMessage();
            }
        }
        result.setCode(ResultCode.FAIL.code());
        result.setMsg(validatorMessage);
        return result;
    }

    @ExceptionHandler({ ConstraintViolationException.class })
    public Result handleCVException(ValidationException e) {
        Result result = new Result();
        String validatorMessage = "";
        if ((e instanceof ConstraintViolationException)) {
            ConstraintViolationException exs = (ConstraintViolationException) e;

            Set<ConstraintViolation<?>> constraintViolations = exs.getConstraintViolations();
            Iterator localIterator = constraintViolations.iterator();
            if (localIterator.hasNext()) {
                ConstraintViolation<?> item = (ConstraintViolation) localIterator.next();
                validatorMessage = item.getMessage();
            }
        }
        result.setCode(ResultCode.FAIL.code());
        result.setMsg(validatorMessage);
        return result;
    }
}

Controller类修改如下:

package com.validator.demo.api.controller;

import javax.validation.Valid;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.validator.demo.api.base.DateResult;
import com.validator.demo.api.model.dto.PeopleDTO;
import com.validator.demo.api.model.vo.PeopleVO;

@RestController
@RequestMapping("/test/api")
public class ValidatorController {
    @PostMapping("/validatortest")
    public DateResult<PeopleVO> test(@RequestBody @Valid PeopleDTO peopleDTO) {
        DateResult<PeopleVO> dateResult = new DateResult<PeopleVO>();

        //具体的业务逻辑
        //省略
        return dateResult;
    }

}

从新调用接口返回如下:

样例demo:Springboot中参数校验优雅实践

猜你喜欢

转载自blog.csdn.net/xinghui_liu/article/details/117258067