在提供对外的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
注解 |
作用 |
|
被注释的元素必须是电子邮箱地址 |
@Length(min=, max=) |
被注释的字符串的大小必须在指定的范围内 |
@NotEmpty |
被注释的字符串的必须非空 |
@Range(min=, max=) |
被注释的元素必须在合适的范围内 |
@NotBlank |
被注释的字符串的必须非空 |
@URL(protocol=, |
被注释的字符串必须是一个有效的url |
@CreditCardNumber |
被注释的字符串必须通过Luhn校验算法, |
@ScriptAssert |
要有Java Scripting API 即JSR 223 |
@SafeHtml |
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中参数校验优雅实践