Springboot参数校验及异常处理
目录
Springboot参数校验及相关异常处理
不同类型参数校验
实体添加校验规则
@Data
public class User {
@NotNull(message = "name不能为空")
private String name;
@Range(min = 0, max = 120, message = "必须在0-120之间")
private int age;
}
通过query string传递的参数
需要在Controller上加上@Validated注解否则不会触发校验规则
@RestController()
@RequestMapping("/validation")
//用于开启query string传参校验,实体对象检测需要单独开启
@Validated
@Slf4j
public class ValidationTestController {
@RequestMapping("/param")
public String testParam(@NotNull(message = "name不能为空") String name, @Range(min = 0, max = 120) int age) throws IOException {
...
}
}
两种form类型的参数
对于multipart/form-data和application/x-www-form-urlencoded类型:
需要在校验实体参数前加上@Validated注解否则不会触发校验规则
@RequestMapping("/formData")
public String testFormData(@Validated User user, @RequestPart("file") MultipartFile file) throws IOException {
...
}
@RequestMapping("/formUrlencoded")
public String testFormUrl(@Validated User user) throws IOException {
...
}
json类型的参数
@RequestMapping("/json")
public String testJson1(@Validated @RequestBody User user) throws IOException {
...
}
参数校验相关异常处理
对于1.1场景校验失败会产生ConstraintViolationException
对于1.2场景校验失败会产生BindException
对于1.3场景校验失败会产生MethodArgumentNotValidException
全局异常处理器参考代码:
/**
* 对于application/json类型请求需要从body中获取,需要处理流只能能读一次问题
* 对于非application/json类型请求,可使用request.getParameterMap()获取参数,request.getParts()获取文件
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/***
* 可能出现的未知异常
* @param e
* @return
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiResult<?> handle(Exception e, ServletRequest request) {
log.error("input:{}", RequestUtils.getInputAsString(request));
log.error(e.getMessage(), e);
return ApiResult.error(e.getMessage());
}
/***
* 参数异常:ConstraintViolationException()
* 用于处理类似url传参[http://xxx?age=30&name=tom]请求中age和name的校验引发的异常
* @param e
* @return
*/
@ExceptionHandler(value = {ConstraintViolationException.class})
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResult<?> urlParametersExceptionHandle(ConstraintViolationException e, ServletRequest request) throws JsonProcessingException {
//可以获取到原始参数
log.error("input:{}", RequestUtils.getInputAsString(request));
log.error(e.getMessage(), e);
//收集所有错误信息,可能有多个不满足条件的字段
List<String> errorMsg = e.getConstraintViolations()
.stream().map(s -> s.getMessage()).collect(Collectors.toList());
return ApiResult.error(BaseResultEnum.INVALID_PARAMETER, errorMsg.toString());
}
/***
* 参数异常: MethodArgumentNotValidException
* MethodArgumentNotValidException: 用于处理请求参数为实体类时校验引发的异常 Content-Type为application/json
* @param e
* @return
*/
@ExceptionHandler(value = {MethodArgumentNotValidException.class})
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResult<?> jsonExceptionHandle(MethodArgumentNotValidException e, ServletRequest request) throws JsonProcessingException {
log.error("input:{}", RequestUtils.getInputAsString(request));
log.error(e.getMessage(), e);
BindingResult bindingResult = e.getBindingResult();
//收集所有错误信息
List<String> errorMsg = bindingResult.getFieldErrors().stream()
.map(s -> s.getDefaultMessage()).collect(Collectors.toList());
return ApiResult.error(BaseResultEnum.INVALID_PARAMETER, errorMsg.toString());
}
/***
* 参数异常: BindException
* BindException: 用于处理请求参数为实体类时校验引发的异常 Content-Type为application/x-www-form-urlencoded或multipart/form-data
* @param e
* @return
*/
@ExceptionHandler(value = {BindException.class})
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResult<?> formExceptionHandle(BindException e, ServletRequest request) throws JsonProcessingException {
log.error("input:{}", RequestUtils.getInputAsString(request));
log.error(e.getMessage(), e);
BindingResult bindingResult = e.getBindingResult();
//收集所有错误信息
List<String> errorMsg = bindingResult.getFieldErrors().stream()
.map(s -> s.getDefaultMessage()).collect(Collectors.toList());
return ApiResult.error(BaseResultEnum.INVALID_PARAMETER, errorMsg.toString());
}
/***
* 自定义异常:自定义异常一般不要设置为ERROR级别,因为我们用自定义的异常主要是为了辅助我们处理业务逻辑
* @param e
* @return
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(value = {BusinessException.class})
@ResponseBody
public ApiResult<?> handleBusinessException(BusinessException e, ServletRequest request) throws JsonProcessingException {
log.error("input:{}", RequestUtils.getInputAsString(request));
log.warn(e.getMessage(), e);
return ApiResult.error(BaseResultEnum.INTERNAL_ERROR, e.getMessage());
}
}
获取校验异常时原始输入参数参考方法:
public abstract class RequestUtils {
private static final ObjectMapper objectMapper = new ObjectMapper();
public static String getParameterMapAsString(ServletRequest request) {
try {
return objectMapper.writeValueAsString(request.getParameterMap());
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public static String getBodyAsString(ServletRequest request) {
if (request instanceof JsonRequestWrapper wrapper) {
return new String(wrapper.getBodyAsByteArray(), StandardCharsets.UTF_8);
}
return "[No Wrapper]";
}
public static String getInputAsString(ServletRequest request) {
if (isJsonRequest(request)) {
return getBodyAsString(request);
} else {
return getParameterMapAsString(request);
}
}
public static boolean isJsonRequest(ServletRequest request) {
String contentType = request.getContentType();
return contentType != null && contentType.contains(MediaType.APPLICATION_JSON_VALUE);
}
}