SpringMVC流程分析(八):SpringMVC中的异常处理

本系列文章皆在分析SpringMVC的核心组件和工作原理,让你从SpringMVC浩如烟海的代码中跳出来,以一种全局的视角来重新审视SpringMVC的工作原理.

前言

截止到目前, 我们沿着dispatch方法调用链逐步对DispatcherServlet中处理http请求的流程进行了分析。

本次,我们将分析doDispatch调用链中最后一个方法processDispatchResult。同时,与该方法相关的组件为HandlerExceptionResolver

processDispatchResult相关逻辑

在分析讨论processDispatchResult讨论之前,我们先看看该方法在doDispatch调用流程。

doDispatch方法

java 复制代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		
	
	ModelAndView mv = null;
	Exception dispatchException = null;
    // 上传逻辑处理
    processedRequest = checkMultipart(request);
    // 寻找对应处理器
    mappedHandler = getHandler(processedRequest);
    
    // ......省略其他无关方法
    
    // 本文重点关注方法
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
			
}

可以看到,对于processDispatchResult方法而言,其会需要五个参数,其中processedRequest,mappedHandler这个我们在之前文章都进行过详细介绍。具体可以参考:

  1. SpringMVC流程分析(三):SpringMVC中处理上传请求的秘密 url: juejin.cn/post/725848...
  2. SpringMVC流程分析(四):SpringMVC中如何为一个请求选择合适的处理器 url: juejin.cn/post/726125...

除了这几个参数外,我们注意到其还会传入一个Exception对象。这就很奇怪了,因为在java中我们对于异常通常会通过try-cath来进行处理,此处将Exception传入方法又是作何处理?

接下来我们便具体分析processDispatchResult内部的相关逻辑。其相关代码如下:

java 复制代码
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

        // ......省略其他无关代码       
		if (exception != null) {
            // 异常为ModelAndViewDefiningException类型,直接从exception中获取异常的View视图
			if (exception instanceof 
            ModelAndViewDefiningException) {
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			} else {
            // 非ModelAndViewDefiningException类型
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                // 生成modelview对象
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}
    // ......省略其他无关代码
}

可以看到processDispatchResult的主要任务在于处理doDispatch处理过中出现的异常信息。 进一步,对于传入processDispatchResult的异常会根据异常类型的不同来选取不同的处理方式。

具体而言,如果异常为ModelAndViewDefiningException类型,直接从Exception中获取异常的ModelAndView进行返回;

如果异常信息为非ModelAndViewDefiningException的异常信息,则会调用processHandlerException进行处理。其相关逻辑如下:

java 复制代码
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			@Nullable Object handler, Exception ex) 
                            throws Exception {

    ModelAndView exMv = null

   
	if (this.handlerExceptionResolvers != null) {
         // 遍历所有的HandlerExceptionResolver处理器
		for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            // 解析异常,⽣成 ModelAndView 对象
			exMv = resolver.resolveException(request, response, handler, ex);
			// 如果解析异常结果不为null则退出遍历
            if (exMv != null) {
				break;
			}
		}
	}
    
    // .... 省略其他无关代码    

    // 情况⼀,⽣成了 ModelAndView 对象,进⾏返回
    if (exMv != null) {
        // ... 省略其他无关代码
        return exMv;
    }
    // 情况⼆,未⽣成 ModelAndView 对象,则抛出异常
    throw ex;
}

可以看到对于processHandlerException方法,其同我们之前讲过的getHanlder、getHandlerAdapter有着类似的逻辑。、

其会遍历容器中所有的 HandlerExceptionResolver。然后依次选取并进行判断,如果某⼀个HandlerExceptionResolver可以成功处理传入的异常信息,则返回 ModelAndView对象。

HandlerExceptionResolver类结构

可以看到在上述方法调用过程中,方法内部会用到一个HandlerExceptionResolver的组件信息,接下来我们便看看这个HandlerExceptionResolverSpringMVC中究竟会完成哪些任务。其结构如下所示:

通过上述类图可以看到,对于HandlerExceptionResolver而言,其有四个默认的实现类。事实上,这些HandlerExceptionResolver的具体实现类,会在SpringMVC初始化时也会默认加载,而无需我们配置。而有关HandlerExceptionResolver初始化的逻辑在DispatcherServlet中的initStrategies的方法内部。

进一步,HandlerExceptionResolver内部所定义方法信息如下所示:

java 复制代码
public interface HandlerExceptionResolver {
    /**
     * 解析异常信息
     * */
	@Nullable
	ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

超级基类:AbstractHandlerExceptionResolver

通过上述的uml类图,我们注意到AbstractHandlerExceptionResolver会实现HandlerExceptionResolver接口,而在该接口内,只有一个resolveException方法,所以我们只需关注AbstractHandlerExceptionResolver中有关方法resolveException实现即可。其相关逻辑如下:

java 复制代码
public ModelAndView resolveException(
			HttpServletRequest request, 
            HttpServletResponse response, @Nullable Object handler, Exception ex) {
        // 判断当前处理器能否解析当前handler信息
		if (shouldApplyTo(request, handler)) {
			prepareResponse(ex, response);
            // 此处doResolveException 为一个抽象方法,具体逻辑交给子类实现,以完成对异常信息的解析和处理
			ModelAndView result = doResolveException(request, response, handler, ex);
			if (result != null) {
                // ... 省略其他方法
				// 日志记录异常方法信息
				logException(ex, request);
			}
			return result;
		}
		else {
            // 如果无法解析则返回一个null
			return null;
		}
	}

可以看到,其首先会调⽤ shouldApplyTo,判断异常处理器是否可以应⽤当前处理器,此时,如果可以应⽤,则调⽤ doResolveExceptio抽象⽅法,执⾏解析异常,并返回ModelAndView 对象,否则则直接返回 null

看到此处的null你一定会更加深刻理解之前processHandlerException为什么可以通过判断返回值是否为null便可以结束对于HandlerExceptionResolver的遍历。

默认实现:DefaultHandlerExceptionResolver

对于HandlerExceptionResolver的实现类,我们仅选取其中的DefaultHandlerExceptionResolver来进行分析,看看其内部的doResolveException都定义了哪些处理逻辑。

(注:对于ResponseStatusExceptionResolver其主要对注解@ResponseStatus注解标注信息进行封装,而ExceptionHandlerExceptionResolver则是对注解@ExceptionHanlder标注的方法进行解析,内部处理逻辑同RequestMappingHandlerAdapter类似,感兴趣的读者可独自进行分析。)

java 复制代码
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 ConversionNotSupportedException) {
			return handleConversionNotSupported(
						(ConversionNotSupportedException) ex, request, response, handler);
		}
	// ...省略其他相似代码		
	}
    // ...省略其他相似代码		
   return null;
}

DefaultHandlerExceptionResolver内部的doResolveException方法逻辑并不复杂,无非就是根据不同的异常类型,然后设置响应码和错误信息。例如 当转换类型不支持时,方法handleConversionNotSupported内部逻辑如下:

java 复制代码
protected ModelAndView handleConversionNotSupported(ConversionNotSupportedException ex,
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {

		sendServerError(ex, request, response);
		return new ModelAndView();
	}

可以看到上述方法会先通过sendServerError设定错误相应的状态码,然后返回一个空的ModelAndView 对象。

这样做的目的是避免其被后续的 ViewResolver 所处理。

总结

本⽂对 Spring MVC 中的 HandlerExceptionResolver 组件进⾏分析,处理器异常解析器,将处理器执⾏时发⽣的异常解析(转换)成对应的 ModelAndView 对象。

同时,HandlerExceptionResolver 的实现类没有特别多,在 Spring MVCSpring Boot 中,默认情况下都有三种 HandlerExceptionResolver实现类,其分别为:

  1. ExceptionHandlerExceptionResolve :处理基于 @ExceptionHandler 配置 HandlerMethod HandlerExceptionResolver 实现

  2. ResponseStatusExceptionResolve: 处理基于 @ResponseStatus 提供错误响应的 HandlerExceptionResolver 实现类

  3. DefaultHandlerExceptionResolver: 默认 HandlerExceptionResolver 实现类。

进一步,我们对其中的 DefaultHandlerExceptionResolver进行了详细的分析,作为默认 HandlerExceptionResolver 实现类,其会针对各种异常, 设置错误响应码。

相关推荐
侠客行03171 天前
Mybatis连接池实现及池化模式
java·mybatis·源码阅读
蛇皮划水怪1 天前
深入浅出LangChain4J
java·langchain·llm
老毛肚1 天前
MyBatis体系结构与工作原理 上篇
java·mybatis
风流倜傥唐伯虎1 天前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
Yvonne爱编码1 天前
JAVA数据结构 DAY6-栈和队列
java·开发语言·数据结构·python
Re.不晚1 天前
JAVA进阶之路——无奖问答挑战1
java·开发语言
你这个代码我看不懂1 天前
@ConditionalOnProperty不直接使用松绑定规则
java·开发语言
fuquxiaoguang1 天前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析
琹箐1 天前
最大堆和最小堆 实现思路
java·开发语言·算法
__WanG1 天前
JavaTuples 库分析
java