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 实现类,其会针对各种异常, 设置错误响应码。

相关推荐
P7进阶路5 分钟前
实现用户登录系统的前后端开发
java
2401_857617626 分钟前
“无缝购物体验”:跨平台网上购物商城的设计与实现
java·开发语言·前端·安全·架构·php
事业运财运爆棚9 分钟前
7种server的服务器处理结构模型
java·linux·服务器
西岭千秋雪_25 分钟前
设计模式の中介者&发布订阅&备忘录模式
java·观察者模式·设计模式·中介者模式·备忘录模式
憶巷31 分钟前
MyBatis中动态SQL执行原理
java·sql·mybatis
重生之绝世牛码32 分钟前
Java设计模式 —— 【结构型模式】享元模式(Flyweight Pattern) 详解
java·大数据·开发语言·设计模式·享元模式·设计原则
seasugar37 分钟前
记一次Maven拉不了包的问题
java·maven
Allen Bright1 小时前
【Java基础-26.1】Java中的方法重载与方法重写:区别与使用场景
java·开发语言
苹果酱05671 小时前
Golang的文件解压技术研究与应用案例
java·vue.js·spring boot·mysql·课程设计
秀儿y1 小时前
单机服务和微服务
java·开发语言·微服务·云原生·架构