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

2023年 8月 13日 40.7k 0

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

前言

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

image.png

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

image.png

processDispatchResult相关逻辑

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

doDispatch方法

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这个我们在之前文章都进行过详细介绍。具体可以参考:

  • SpringMVC流程分析(三):SpringMVC中处理上传请求的秘密
    url: juejin.cn/post/725848…
  • SpringMVC流程分析(四):SpringMVC中如何为一个请求选择合适的处理器
    url: juejin.cn/post/726125…
  • 除了这几个参数外,我们注意到其还会传入一个Exception对象。这就很奇怪了,因为在java中我们对于异常通常会通过try-cath来进行处理,此处将Exception传入方法又是作何处理?

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

    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进行处理。其相关逻辑如下:

    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内部所定义方法信息如下所示:

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

    超级基类:AbstractHandlerExceptionResolver

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

    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类似,感兴趣的读者可独自进行分析。)

    
    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内部逻辑如下:

    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实现类,其分别为:

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

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

  • DefaultHandlerExceptionResolver: 默认 HandlerExceptionResolver 实现类。

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

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论