SpringBoot的2.3版本之前,在web-starter中自带了校验依赖,从2.3开始需要手动添加:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
web层的校验
大部分情况下,校验都集中在web层进行,对controller方法的入参进行校验,代码片段如下:
@PostMapping(value = "/add")
public ResponseEntity addStu(@Valid Student student) {
log.info("addStu student={}", student);
return ResponseEntity.ok(student);
}
@PostMapping(value = "/update")
public ResponseEntity updateStu(@Valid @RequestBody Student student) {
log.info("updateStu student={}", student);
return ResponseEntity.ok(student);
}
BindException
第一个方法的入参使用了Spring的数据绑定,在@Valid入参后面还可以加上一个Errors类型或者BindingResult类型的入参,获取左边入参的校验异常信息。
但是每次这样手动收集异常信息很重复,数据绑定发生异常时,默认会抛出BindException。通常使用@RestControllerAdvice或@ControllerAdvice来进行统一异常处理。
MethodArgumentNotValidException
第二个方法,对请求体中json格式的数据转换而来的对象进行校验。如果校验失败,默认情况下会抛出MethodArgumentNotValidException异常。
同样,这样的方法也可以在后面跟上Errors或BindingResult对象。并且使用@RestControllerAdvice或@ControllerAdvice来进行统一异常处理更好。
统一的异常处理类
代码如下:
@RestControllerAdvice
@Slf4j
public class CustomExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity handleException(Exception e){
log.error("handleException", e);
return ResponseEntity.ok("操作失败,"+e.getMessage());
}
@ExceptionHandler(BindException.class)
public ResponseEntity handleBindException(BindException e) {
log.error("handleBindException", e);
String msg = e.getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.joining(";"));
return ResponseEntity.ok(msg);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("handleMethodArgumentNotValidException", e);
String msg = e.getBindingResult().getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.joining(";"));
return ResponseEntity.ok(msg);
}
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity handleConstraintViolationException(ConstraintViolationException e) {
log.error("handleConstraintViolationException", e);
String msg = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage)
.collect(Collectors.joining(";"));
return ResponseEntity.ok(msg);
}
}
非web层校验
在其它的地方,比如对service层的入参进行校验有时也是必要的。
发生校验的方法的对象,都是bean,并且通过AOP来完成校验。
ConstraintViolationException
bean可以直接注入一个Validator对象,然后手写代码进行校验,校验失败会抛出ConstraintViolationException。这样操作会比较繁琐,可以在统一异常处理类中处理ConstraintViolationException。
通过查看官方文档的Spring-driven Method Validation(core 3.7-Java Bean Validation章节下),为了能够进行Spring驱动的方法校验,所有的目标类都需要标记上@Validated注解。代码如下:
@Validated
@Service
public class ValidationService {
public void print(@NotBlank(message = "参数不可为空") String msg) {
System.out.println(msg);
}
/**
* 如果想校验student内部的字段,需要加上@Valid;不加的话,则只会进行@NotNull校验。
* @param student
*/
public void printStu(@NotNull(message = "学生信息不可为空") Student student) {
System.out.println(student);
}
}
这些方法不支持web层校验的Errors和BindingResult,校验失败会抛出ConstraintViolationException。
@RequestParam如何校验
如果入参使用了@RequestParam该如何校验呢?可以通过上述的Spring-driven Method Validation来实现。
即在Controller类上加上@Validated注解,就可以进行校验。代码如下:
@GetMapping(value = "/getStu")
public Student getStu(@RequestParam @NotBlank(message = "姓名不可为空") String name) {
Student student = new Student();
return student;
}
如果传入的name为空,就会抛出ConstraintViolationException。
需要注意的是对于数据绑定的入参或者@RequestBody注解的入参,处理方法没使用Errors和BindingResult,校验失败会抛出BindException;如果处理方法的入参使用了Errors或BindingResult的话,会变成抛出ConstraintViolationException。
在Controller上使用Spring-driven Method Validation,可能会让人有点混淆。所以最好不这样做,参数较少可以直接校验,较多的话使用数据绑定的对象。
@Validated和@Valid的异同
其实在数据绑定入参或者@RequestBody的入参中,@Validated和@Valid可以互相混用。但是它们还是有点区别。
@Valid支持嵌套校验而@Validated不支持,即一个类对象的字段还是一个类对象或者是集合,只需在包装类的字段上加上@Valid就可以校验被包装类。
@Validated支持分组校验,而@Valid不支持。分组校验就是在不同情况下,对同一个字段可以采取不同的校验规则。
参考连接:https://segmentfault.com/a/1190000023451809#item-7
异常相关注解@ResponseStatus
该注解用于设置状态码,还有一个可配置的属性reason,用来在错误页面上展示的文字信息。
但是该注解并不适用于REST APIs的风格。