上次大概跟踪了下SpringBoot抛异常和异常截获的原理(https://blog.csdn.net/qq_27062249/article/details/118316320),中间还有一个问题,就是在SpringBoot解析异常的时候还有一个DefaultHandlerExceptionResolver,这个错误解析器就在解析器数组的index=2的位置,也就是前面两个错误解析器处理了的话,就会返回不会轮到跑DefaultHandlerExceptionResolver的处理逻辑了
DefaultHandlerExceptionResolver会处理那些出现的异常呢?都在DefaultHandlerExceptionResolver的doResolveException( )方法里,这里有HttpRequestMethodNotSupportedException(目标方法是一个get方法的话,当客户端发送的是post方法,这是就会抛出这个异常)、HttpMediaTypeNotSupportedException(比如方法上名明确的标注了接受json格式的参数客户端传的formData的格式,就会抛出这个异常)、NoHandlerFoundException(url路径输错时候出现的404)、MissingServletRequestPartException(服务端必传的参数,前端没传时候抛出的400)、MethodArgumentNotValidException(使用validation-api校验框架没有没通过校验时候抛出的异常。最后使用这个优化实际案例)等等
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported(
(HttpRequestMethodNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported(
(HttpMediaTypeNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
return handleHttpMediaTypeNotAcceptable(
(HttpMediaTypeNotAcceptableException) ex, request, response, handler);
}
else if (ex instanceof MissingPathVariableException) {
return handleMissingPathVariable(
(MissingPathVariableException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestParameterException) {
return handleMissingServletRequestParameter(
(MissingServletRequestParameterException) ex, request, response, handler);
}
else if (ex instanceof ServletRequestBindingException) {
return handleServletRequestBindingException(
(ServletRequestBindingException) ex, request, response, handler);
}
else if (ex instanceof ConversionNotSupportedException) {
return handleConversionNotSupported(
(ConversionNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof TypeMismatchException) {
return handleTypeMismatch(
(TypeMismatchException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotReadableException) {
return handleHttpMessageNotReadable(
(HttpMessageNotReadableException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotWritableException) {
return handleHttpMessageNotWritable(
(HttpMessageNotWritableException) ex, request, response, handler);
}
else if (ex instanceof MethodArgumentNotValidException) {
return handleMethodArgumentNotValidException(
(MethodArgumentNotValidException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestPartException) {
return handleMissingServletRequestPartException(
(MissingServletRequestPartException) ex, request, response, handler);
}
else if (ex instanceof BindException) {
return handleBindException((BindException) ex, request, response, handler);
}
else if (ex instanceof NoHandlerFoundException) {
return handleNoHandlerFoundException(
(NoHandlerFoundException) ex, request, response, handler);
}
else if (ex instanceof AsyncRequestTimeoutException) {
return handleAsyncRequestTimeoutException(
(AsyncRequestTimeoutException) ex, request, response, handler);
}
}
catch (Exception handlerEx) {
if (logger.isWarnEnabled()) {
logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
}
}
return null;
}
真实案例
清楚了上面SpringBoot处理系统默认异常的方式,结合平常开发时请求参数的判空等一些案例,我们来修改的更加方便一点
参数判空的实际案例(这个是我上一家公司真实的项目中截取过来的。这些会不会很累?每个方法都需要判断,如果需要修改的话每一个方法都要修改一遍)
/**
* 获取绘本在线播放资源 url
*
* @param mac 设备mac地址
* @param bookId 绘本id
* @param readModel 阅读模式:1->领读,2->指读
* @param fileName 文件名
* @return
*/
@PostMapping("/query-single")
public Result query(String mac, Long bookId, Integer readModel, String fileName) {
Assert.isTrue(mac != null, "mac不能为空!!!");
Assert.isTrue(bookId != null, "bookId不能为空!!!");
Assert.isTrue(readModel != null, "阅读模式不能为空:1为领读,2为指读");
Assert.isTrue(fileName != null, "资源名称不能为空!!!");
return macClient.querySingle(mac, bookId, readModel, fileName);
}
/**
* 获取绘本资源包下载链接
*
* @param mac 设备mac地址
* @param bookId 绘本id
* @return
*/
@PostMapping("/query-package")
public Result queryPackage(String mac, Long bookId, Long channelId) {
Assert.isTrue(mac != null, "mac不能为空!!!");
Assert.isTrue(bookId != null, "bookId不能为空!!!");
Assert.isTrue(channelId != null, "渠道Id不能为空!!!");
JSONObject obj = macClient.queryPackage(mac, bookId, channelId);
Integer code = obj.getInteger("code");
return code.equals(1) ? ResultUtils.returnSuccess(obj) : (code == 0 ? ResultUtils.returnError("Mac地址:" + mac + "不合法!!") : ResultUtils.returnError("bookId:" + bookId + ",资源不存在!!"));
}
优化方案
我们最终想实现一个:
- 验证逻辑和提示信息只需要在一处编写
- 如果请求参数的验证规则有变化的话只需要在修改
引入基于注解的验证框架
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>
自定义异常捕获组件,捕获MethodArgumentNotValidException
@ControllerAdvice
public class CustomExceptionHandler {
/**
* 请求参数验证异常时返回
*
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultUtils.Result handler(MethodArgumentNotValidException e) {
String parameterName = e.getParameter().getParameterName();
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return ResultUtils.returnError(parameterName + message);
}
}
将请求参数封装成一个dto(这里就用一个简单的类测试下)
@Data
public class User {
@NotBlank(message = "name参数不能空")
private String name;
@Max(100)
private Integer age;
}
实际接口方法上(@RequestBody是从请求体读取参数,@Validated开启验证,不标注触发不了验证)
@PostMapping(value = "/args",consumes = MediaType.APPLICATION_JSON_VALUE,produces = MediaType.APPLICATION_JSON_VALUE)
public ResultUtils.Result getRmoteProviderProt5(@RequestBody @Validated User user) {
return ResultUtils.returnSuccess(providerFeign.getProt5(user));
}
postman请求测试案例
name参数不符合验证规则
age参数不符合验证规则
这样一来,如果我们在多个方法上使用到User作为前端的入参的时候,我们需要修改验证的规则就只需要在User这一个类里面修改了。