Spring Boot 支持 JSR-303 、 Bean 验证框架 , 默认实现用的是 Hibernate validator。在Spring MVC 中,只需要使用@Valid 注解标注在方法参数上, Spring Boot 即可对参数对象进行校验 ,校验结果放在BindingResult 对象中。
JSR-303 是 Java 标准的验证框架 , 己有的实现有 Hibernate validator . JSR-303 定义了一系列注解用来验证 Bean 的属性,常用的有如下几种。
空检查
长度检查
数值检测
其他
以下是一个包含了验证注解的 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 匹配的时候,校验注解才能生效。
在 Controller 中 , 只需要给方法参数加上@Validated 即可触发一次校验 。
参数使用了@Validated 注解,将触发 Spring 的校验 , 井将验证结果存放到 BindingResult 对象中 。 这里, Validated 注解使用了校验的上下文 User.Add.class , 因 此, 整个校验将按照 Add.class 来校验。
BindingResult 包含了验证结果,提供了如下方法 :
如果 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";
}
}
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());
}
}
JSR-303 提供的大部分校验注解已经够用,也允许定制校验注解,比如在 User类中,我们新增一个加班时间:
@WorkOverTime(max=2)
int workTime ;
属性 workTime 使用了注解@WorkOverTime,当属性值超过 max 值的时候,将会验证失败 。
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类来进行验证。验证注解必须要提供如下信息:
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;
}
}
把验证方法写到父类中,所有需要验证的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 全局异常处理三种方式》进行异常处理即可。
如果觉得本文对您有帮助,请点赞支持,非常感谢;