当前位置: 首页 > news >正文

ExceptionHandler的实践

ExceptionHandler的实践

  • 1、ExceptionHandler中ModelAndView返回一个不存在的view,会发生什么
  • 2、验证
  • 3、view不存在为什么会抛异常
  • 4、正确写全局异常捕获类实例

1、ExceptionHandler中ModelAndView返回一个不存在的view,会发生什么

背景: 公司的项目中经常会抛异常,然后就排查为什么会经常有异常,最终通过异常堆栈等信息了解到工程中使用了全局异常捕获,返回的是ModelAndView对象,但是这里指定的view工程上找不到。

结论: 在全局异常捕获方法【ExceptionHandler】中,如果返回的是ModelAndView,但是view返回一个不存在view,会发生什么?

  • 很明显会直接抛异常给调用端。

接下来看,为什么会抛异常?

2、验证

代码复现:

// http接口@RestController
@RequestMapping(value = "/api")
public class SecondFloorController {@GetMapping("/list")@ResponseBodypublic BaseResponse getData(HttpServletRequest request) throws Exception {RequestParam requestParam = new RequestParam(request);long userId = requestParam.getUserId();// 只是为了测试,并不是真正的业务逻辑if(userId == 0){return new BaseResponse(0, "success");} else if (userId == 1) {throw new EofException();} else {throw new IOException();}}
}// 全局异常捕获
@ControllerAdvice
public class ExceptionHandle {private static Logger LOGGER = LoggerFactory.getLogger(ExceptionHandle.class);@ExceptionHandler(IOException.class)public ModelAndView errorPage(Exception e) {ModelAndView mav = new ModelAndView("error");  // 这里工程中没有error.index、error.jsp等文件mav.addObject("status", ResponseStatusEnum.TIMEOUT.getCode());mav.addObject("error", ResponseStatusEnum.TIMEOUT.getMessage());return mav;}@ExceptionHandler(EofException.class)public BaseResponse error(Exception e) {BaseResponse response = new BaseResponse(-2, "EofException");return response;}}

全局异常捕获方法中返回不同数据,运行结果现象:

返回数据请求结果现象
返回modelAndView在遇到指定view但是不存在$1600
返回自定义结构体$1600包装成ModelAndView,view是请求路径,model就是返回的自定义结构体
在全局异常捕获类中增加ResponseBody注解在这里插入图片描述只要在全局异常捕获类里面增加ResponseBody注解,上述情况也没问题【但不要返回ModelAndView】

结论:

  • 排查发现:ExceptionHandle类中没有添加@ResponseBody注解:
    • 如果ExceptionHandle方法返回的自定义数据都会被Spring包装成ModelAndView,view默认是请求路径,但是view不存在就会抛异常
    • 如果ExceptionHandle方法返回的是ModelAndView,如果view不存在也会抛异常

3、view不存在为什么会抛异常

一言不合,再打开源码瞅瞅:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (logger.isDebugEnabled()) {logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);}if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.// 这里会执行业务代码,就是你的controller,并且将返回结果通过jetty发送给客户端【这时候可能会出现EofException,但是会被封装成HttpMessageNotWritableException】mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);}// 通过这里可以看出,业务执行的代码不管怎么抛异常都会被捕获,所以doDispatch怎么才会抛异常给上层呢catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {// As of 4.3, we're processing Errors thrown from handler methods as well,// making them available for @ExceptionHandler methods and other scenarios.dispatchException = new NestedServletException("Handler dispatch failed", err);}// 无论业务是否正常返回还是给客户端发送数据是否成功都会被执行,唯一有可能抛异常的就是这里了processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}// 这里虽然catch了,但是可以看下triggerAfterCompletion会原封不动的继续抛出来catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}

processDispatchResult方法:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);// 这里会按照优先级获取执行全局异常捕获方法,会封装成ModelAndView// 全局异常捕获类中没有增加ResponseBody注解,你返回自定义结构体,这里会把结果封装为model,view为路径// 全局异常捕获类中增加ResponseBody注解,你却返回ModelAndView,这里是返回的结果【等价于ResponseBody注解没生效】mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// Did the handler return a view to render?if (mv != null && !mv.wasCleared()) {// 这里会对view进行渲染,如果view不存在直接抛异常【根因,到此结束】render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}else {if (logger.isDebugEnabled()) {logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +"': assuming HandlerAdapter completed request handling");}}if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Concurrent handling started during a forwardreturn;}if (mappedHandler != null) {mappedHandler.triggerAfterCompletion(request, response, null);}}

4、正确写全局异常捕获类实例

@ControllerAdvice
@ResponseBody
public class ExceptionHandle {private static Logger LOGGER = LoggerFactory.getLogger(ExceptionHandle.class);/*** 虽然可行 但是不要这么干!!!*/@ExceptionHandler(IOException.class)public ModelAndView errorPage(Exception e) {ModelAndView mav = new ModelAndView("error");mav.addObject("status", ResponseStatusEnum.TIMEOUT.getCode());mav.addObject("error", ResponseStatusEnum.TIMEOUT.getMessage());return mav;}@ExceptionHandler(EofException.class)public BaseResponse eofExceptionHandler(Exception e) {BaseResponse response = new BaseResponse(-1, "EofException");return response;}@ExceptionHandlerpublic BaseResponse exceptionHandler(Exception e) {BaseResponse response = new BaseResponse(-1, e.getMessage());return response;}
}

顺便说句:

如果Controller中增加了@MdpExceptionHandler(EofException.class),那么Controller会优先使用自己controller下的异常捕获。


http://www.mrgr.cn/news/68398.html

相关文章:

  • 江协科技STM32学习- P36 SPI通信外设
  • 处理 omitted for duplicate 灰色
  • 【数据集】【YOLO】【目标检测】道路结冰数据集 1527 张,YOLO目标检测实战训练教程!
  • 利用亚马逊AWS IoT核心和MQTT进行数据采集的综合指南
  • 王健林,被好兄弟追债89亿
  • Docker实操:安装MySQL5.7详解(保姆级教程)
  • 搭子小程序定制开发:全新找搭子之旅
  • 2024年高校辅导员考试题库及答案
  • 工具导航网址
  • 理解Web登录机制:会话管理与跟踪技术解析(四)-拦截器Interceptor、异常处理
  • oracle服务器意外宕机数据库启动失败故障处理记录
  • 丹摩征文活动 | 带你玩转开源模型,一起来部署SD3+ComfyUI文生图模型
  • 开源项目工具:LeanTween - 为Unity 3D打造的高效缓动引擎详解(比较麻烦的API版)之二———补间动画控制API系列
  • 【DL】YOLO11 OBB目标检测 | 模型训练 | 推理
  • openlunar 通过uio驱动直接修改映射内存读写权限
  • 方正阀门实控人去年分红近亿元:仍募资补流2500万,市场服务费疑点重重
  • 【人工智能】ChatGPT多模型感知态识别
  • 丹摩征文活动|详解 DAMODEL(丹摩智算)平台:为 AI 开发者量身打造的智算云服务
  • PyQt5实战——翻译的实现,成功爬取微软翻译(可长期使用)经验总结(九)
  • 初识网络编程TCP/IP
  • vue反向代理配置及宝塔配置
  • 【从零开始】5. 向量数据库选型与搭建
  • 在python中,什么是库?
  • 微服务架构面试内容整理-Archaius
  • 丹摩征文活动|FLUX.1图像生成模型:AI工程师的创新实践
  • 157页全面介绍票据业务