JDK validation 功能(spring)
小于 1 分钟
对于 JDK validation,Spring 做了封装 spring-boot-starter-validation
,引用于如 spring-boot-starter-web
项目中。
校验捕获
在 web 项目中,通过接收 BindingResult 参数来处理请求中的参数校验异常。
处理类
package org.example.handler;
import org.example.entity.R;
import org.example.entity.UserParam;
import org.example.entity.UserVo;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import javax.validation.groups.Default;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 整理 {@link Valid} 和 {@link Validated} 的用法
*/
@RestController
@RequestMapping("/user")
@Validated // 💡在类上注解,开启方法参数校验(即:校验伴随校验注解的参数)
public class UserHandler {
/**
* 使用 {@link Valid} 触发校验,将全部校验失败结果存入 {@link BindingResult}
*/
@GetMapping("/getUser")
public R getUser(@RequestBody @Valid UserParam param, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return handleValidatedErrors(bindingResult);
}
UserVo userVo = UserVo.builder()
.id(param.getId())
.username(param.getUsername())
.build();
return R.SUCCESS(userVo);
}
/**
* {@link Validated} 优点一:在方法参数中使用,可以指定校验 “分组” 功能
*/
@PostMapping("/insertUser")
public R insertUser(@RequestBody @Validated(value = {UserParam.Insert.class, Default.class}) UserParam param,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return handleValidatedErrors(bindingResult);
}
return R.SUCCESS("ok");
}
/**
* {@link Validated} 优点二:在直接对方法参数开启校验功能
* (对校验失败的抛出 {@link javax.validation.ConstraintViolationException} 异常;
* 对参数转换错误的抛出 {@link org.springframework.web.method.annotation.MethodArgumentTypeMismatchException} 异常;
* 参数缺失异常 {@link org.springframework.web.bind.MissingServletRequestParameterException})
*/
@GetMapping("/reflect")
public R reflect(@RequestParam("id") @NotNull @Positive Integer num) {
return R.SUCCESS(num);
}
private static R handleValidatedErrors(BindingResult bindingResult) {
List<Map<String, Object>> errors = bindingResult.getAllErrors()
.stream().map(e -> {
Map<String, Object> map = new HashMap<>();
map.put("ObjectName", e.getObjectName());
map.put("DefaultMessage", e.getDefaultMessage());
// map.put("Arguments", e.getArguments()); // 全部入参
if (e instanceof FieldError) {
map.put("Field", ((FieldError) e).getField());
map.put("RejectedValue", ((FieldError) e).getRejectedValue());
}
return map;
}).collect(Collectors.toList());
return R.ERROR(errors);
}
}
校验类
package org.example.entity;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class UserParam {
public static interface Insert { }
@NotBlank
private String id;
@NotBlank(groups = {
Insert.class
})
private String username;
}
全局拦截校验异常
在 web 项目中,通过 @ControllerAdvice
和 @ExceptionHandler({BindException.class})
注解可以拦截校验异常。
但对于不同请求、不同参数接受方式,拦截异常不同:
- 当
@NotNull
/Positive
这些注解直接修饰参数时,抛出ConstraintViolationException
异常。 (💡 这些注解直接修饰的话,需要在类上添加@Validated
注解,校验才会工作) - 当
@Valid
/@Validated
修饰@RequestBody
参数时,抛出MethodArgumentNotValidException
异常。 - 当
@Valid
/@Validated
修饰非@RequestBody
参数时,抛出BindException
异常。
处理类
package org.example.handler;
import org.example.entity.R;
import org.example.entity.TaskParam;
import org.example.entity.TaskVo;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 整理校验异常
*/
@RestController
@RequestMapping("/task")
@Validated
public class TaskHandler {
/**
* 对于 {@link RequestParam}/{@link PathVariable} 修饰的 URL 参数:
* 在类上用 {@link Validated} 修饰且参数用 {@link javax.validation.Constraint} 修饰时,
* 校验失败将抛出 {@link ConstraintViolationException} 异常
*/
@GetMapping("/param")
public R param(@RequestParam("num") @Positive @NotNull Integer num) {
return R.SUCCESS(num);
}
@GetMapping("/param/{num}")
public R paramPath(@PathVariable("num") @Positive @NotNull Integer num) {
return R.SUCCESS(num);
}
/**
* 对于默认的表单参数:
* 参数用 {@link Valid}/{@link Validated} 修饰时、
* 校验失败将抛出 {@link BindException} 异常
*/
@GetMapping("/formValid")
public R formValid(@Valid TaskParam param) {
TaskVo taskVo = TaskVo.builder()
.id(param.getId())
.build();
return R.SUCCESS(taskVo);
}
@GetMapping("/formValidated")
public R formValidated(@Validated TaskParam param) {
TaskVo taskVo = TaskVo.builder()
.id(param.getId())
.build();
return R.SUCCESS(taskVo);
}
/**
* 对于 {@link RequestBody} 修饰的 json 参数:
* 参数用 {@link Valid}/{@link Validated} 修饰时、
* 校验失败将抛出 {@link MethodArgumentNotValidException} 异常
*/
@GetMapping("/jsonValid")
public R jsonValid(@RequestBody @Valid TaskParam param) {
TaskVo taskVo = TaskVo.builder()
.id(param.getId())
.build();
return R.SUCCESS(taskVo);
}
@GetMapping("/jsonValidated")
public R jsonValidated(@RequestBody @Validated TaskParam param) {
TaskVo taskVo = TaskVo.builder()
.id(param.getId())
.build();
return R.SUCCESS(taskVo);
}
/**
* 通过 {@link ExceptionHandler} 拦截当前类的 {@link ConstraintViolationException} 异常
* (如果考虑拦截全部 handler 异常,可以将 {@link ExceptionHandler} 定义在 {@link ControllerAdvice} 修饰的类中)
*/
@ExceptionHandler({ConstraintViolationException.class}) // url 参数提交
public R handleConstraintViolationException(ConstraintViolationException exception) {
Set<ConstraintViolation<?>> constraintViolations = exception.getConstraintViolations();
List<Map<String, Object>> collect = constraintViolations.stream().map(e -> {
Map<String, Object> map = new HashMap<>();
map.put("PropertyPath", e.getPropertyPath().toString());
map.put("InvalidValue", e.getInvalidValue());
map.put("Message", e.getMessage());
return map;
}).collect(Collectors.toList());
return R.ERROR(collect);
}
@ExceptionHandler({BindException.class}) // 表单提交
public R handleBindException(BindException exception) {
BindingResult bindingResult = exception.getBindingResult();
return getR(bindingResult);
}
@ExceptionHandler({MethodArgumentNotValidException.class}) // json 提交
public R handleBindException(MethodArgumentNotValidException exception) {
BindingResult bindingResult = exception.getBindingResult();
return getR(bindingResult);
}
private static R getR(BindingResult bindingResult) {
List<Map<String, Object>> errors = bindingResult.getAllErrors()
.stream().map(e -> {
Map<String, Object> map = new HashMap<>();
map.put("ObjectName", e.getObjectName());
map.put("DefaultMessage", e.getDefaultMessage());
// map.put("Arguments", e.getArguments()); // 全部入参
if (e instanceof FieldError) {
map.put("Field", ((FieldError) e).getField());
map.put("RejectedValue", ((FieldError) e).getRejectedValue());
}
return map;
}).collect(Collectors.toList());
return R.ERROR(errors);
}
}
校验类
package org.example.entity;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class TaskParam {
@NotBlank
private String id;
}