首页 > 其他分享 >ExceptionHandler的实践

ExceptionHandler的实践

时间:2024-11-09 17:17:48浏览次数:3  
标签:mappedHandler request ExceptionHandler 实践 ModelAndView processedRequest response

ExceptionHandler的实践

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

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

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

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

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

2、验证

代码复现:

// http接口

@RestController
@RequestMapping(value = "/api")
public class SecondFloorController {
	@GetMapping("/list")
    @ResponseBody
    public 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 afterCompletion
				if (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 forward
			return;
		}

		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;
    }
  
  	@ExceptionHandler
    public BaseResponse exceptionHandler(Exception e) {
        BaseResponse response = new BaseResponse(-1, e.getMessage());
        return response;
    }
}

顺便说句:

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

标签:mappedHandler,request,ExceptionHandler,实践,ModelAndView,processedRequest,response
From: https://blog.csdn.net/m0_50149847/article/details/143616938

相关文章

  • 机器学习实战:从理论到实践的探索之旅
    机器学习实战:从理论到实践的探索之旅在当今这个数据驱动的时代,机器学习作为人工智能的一个核心分支,正以前所未有的速度改变着我们的生活和工作方式。从智能推荐系统到自动驾驶汽车,从医疗诊断到金融风险评估,机器学习技术无处不在,其强大的数据处理和模式识别能力为各行各业带......
  • 深入理解JUnit注解:从入门到最佳实践
    深入理解JUnit注解:从入门到最佳实践一、JUnit注解基础概览1.五大核心注解JUnit提供了五个最基本也是最常用的注解:@Test@Before@After@BeforeClass@AfterClass让我们通过一个完整的示例来了解这些注解:publicclassBankAccountTest{privatestaticDatabaseConn......
  • 如何在微服务架构中优化微信 Access Token 管理:解决频率限制与过期问题的最佳实践
    问题描述在微信小程序或公众号的开发中,AccessToken是调用微信接口的关键凭证。然而,由于微信对AccessToken的访问频率和刷新操作有严格的限制(每个AccessToken有效期为2小时,刷新频率为2000次/天),微服务架构中多个服务或实例可能会频繁请求AccessToken,导致访问频......
  • 书生大模型实战营第四期 L1G5000 XTuner 微调实践微调
    XTuner微调实践微调文章目录XTuner微调实践微调前言一、环境配置与数据准备修改提供的数据训练启动模型WebUI对话前言针对业务场景(如特殊自我认知的机器人)的微调能力一个属于自己的语言聊天机器人一、环境配置与数据准备本节中,我们将演示如何安装XTuner。......
  • 鸿蒙项目实战(三):自定义弹窗开发实践
    自定义弹窗选型合理选择不同的系统能力实现弹窗,有利于提升应用开发效率,实现更好的功能需求,因此了解自定义弹窗的选型和差异非常重要。在应用开发中,为了选择出合适的弹窗选型,从使用场景上,需要重点关注以下两点:弹窗与界面代码解耦在开发业务逻辑时,例如遇到一些网络请求失败的场......
  • 鸿蒙开发进阶(HarmonyOS)原生能力设备唯一ID实践
      鸿蒙NEXT开发实战往期必看文章:一分钟了解”纯血版!鸿蒙HarmonyOSNext应用开发!“非常详细的”鸿蒙HarmonyOSNext应用开发学习路线!(从零基础入门到精通)HarmonyOSNEXT应用开发案例实践总结合(持续更新......)HarmonyOSNEXT应用开发性能优化实践总结(持续更新......)场景......
  • 腾讯云CVM云服务器最佳实践
    引言在数字化时代,云计算已成为企业和个人用户部署应用、存储数据、处理信息的首选方案,其高效、灵活、可扩展的特性极大地促进了业务的快速发展,那么如何正确地购买、配置、管理云服务器,以确保其安全稳定运行呢?本文将以腾讯云CVM云服务器的最佳实践为例,说明从购买到初始化设......
  • 从零开始: MongoDB 原理到实践一站式解决
    文档帮你翻译好了,让你一站式上手MongoDBMongoDB的设计理念和基本架构:MongoDB是什么?简单理解:-一个存储数据的数据库-数据格式像JSON-不需要固定的表结构-容易扩展和维护类比:-传统数据库像精心设计的档案柜-MongoDB像自由组合的收纳盒核心概念对比M......
  • 复制下来就能跑:Java智能问答系统-介绍与代码实践 - 基于springboot_springai_国产大模
    本文的目的是在5分钟内能把智能问答系统的原理和实践讲明白代码可执行,复制粘贴即可,可以快速跑起来。智能问答系统简介智能问答系统是一种人工智能应用,它能够理解用户提出的问题,并通过自然语言处理技术来分析和理解问题的含义。随后,系统会在其知识库中搜索相关信息,以生成......
  • 从单层到 MVC,再到 DDD:架构演进的思考与实践
    引言在日常开发中,我们之前工作中经常接手的大多数都是传统MVC架构体系的项目。然而,随着现在分布式和微服务架构的普及,越来越多的项目开始重构、拆分,传统的MVC架构也逐渐向DDD架构演进。为什么需要将传统架构重构为DDD架构?MVC架构相比如今备受关注的DDD架构又有......