第三十讲-SpringMVC中的异常处理

第三十讲-SpringMVC中的异常处理

SpringMVC在执行HandlerAdapter的过程中,也就是执行控制器方法的过程中,例如参数解析,参数名解析,返回值解析,类型转换。数据绑定时的任何一步一旦出现异常,就会立即捕捉该异常,并进行处理!那么在异常注意过程中究竟干了哪些事呢?我们可以看一下对应的方法:

@Nullable
	protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			@Nullable Object handler, Exception ex) throws Exception {

		// Success and error responses may use different content types
		request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

		// Check registered HandlerExceptionResolvers...
		ModelAndView exMv = null;
        // 如果捕获到了异常,则继续宁异常处理(注意可能会有多个异常,针对不同的异常,都需要单独的处理!)
		if (this.handlerExceptionResolvers != null) {
			for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
				exMv = resolver.resolveException(request, response, handler, ex);
				if (exMv != null) {
					break;
				}
			}
		}
		if (exMv != null) {
			if (exMv.isEmpty()) {
				request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
				return null;
			}
			// We might still need view name translation for a plain error model...
			if (!exMv.hasView()) {
				String defaultViewName = getDefaultViewName(request);
				if (defaultViewName != null) {
					exMv.setViewName(defaultViewName);
				}
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Using resolved error view: " + exMv, ex);
			}
			else if (logger.isDebugEnabled()) {
				logger.debug("Using resolved error view: " + exMv);
			}
			WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
			return exMv;
		}

		throw ex;
	}

1. 关于返回JSON类型时的异常处理

由于SpringMVC在执行过程当中可能会出现各种异常,这里呢,我们并不会一一讲解,而是挑一些比较重要的异常处理器来分析,例如我们平时开发过程中使用的自定义异常(@ExceptionHandler),@ExceptionHandler注解是由ExceptionHandlerExceptionResolver来解析处理的。下面我们通过一个例子来看一下@ExceptionHandler的用法:

public class A30 {
    public static void main(String[] args) throws NoSuchMethodException {
        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
        // 添加必要的参数解析器和返回值处理器
        // 添加一个消息转换器:JAVA<->JSON
        resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
        // 添加一个参数解析器和返回值处理器
        resolver.afterPropertiesSet();
        // 测试JSON
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        // 准备控制器方法
        HandlerMethod handlerMethod = new HandlerMethod(new Controller1(), Controller1.class.getDeclaredMethod("foo"));
        // 准备一个异常对象
        Exception e = new ArithmeticException("被零除");
        resolver.resolveException(request, response, handlerMethod, e);
        // 打印response响应的数据
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
    }

    static class Controller1 {
        public void foo() {

        }
        @ExceptionHandler
        @ResponseBody
        public Map<String, Object> handle(ArithmeticException e) {
            return Map.of("error", e.getMessage());
        }
    }
    
}

如上面的代码,如果handlerMethod能够在指定的控制器类和控制方法在执行的过程中捕获到异常,则会由@ExceptionHandler注解标注的异常处理方法来处理。

上面的代码执行结果为:

18:37:54.204 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Using @ExceptionHandler com.cherry.a30.A30$Controller1#handle(ArithmeticException)
18:37:54.251 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Using 'application/json', given [*/*] and supported [application/json, application/*+json]
18:37:54.254 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Writing [{error=被零除}]
18:37:54.316 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Resolved [java.lang.ArithmeticException: 被零除]
{"error":"被零除"}

2. 关于返回ModelAndView类型时的异常处理

接下来我们看一下返回值类型为ModelAndView类型时的异常处理,如下面的代码:

public class A30 {
    public static void main(String[] args) throws NoSuchMethodException {
        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
        // 添加必要的参数解析器和返回值处理器
        // 添加一个消息转换器:JAVA<->JSON
        resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
        // 添加一个参数解析器和返回值处理器
        resolver.afterPropertiesSet();
        // 测试JSON
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();

        HandlerMethod handlerMethod = new HandlerMethod(new Controller2(), Controller2.class.getDeclaredMethod("foo"));
        Exception e = new ArithmeticException("被零除");
        ModelAndView mav = resolver.resolveException(request, response, handlerMethod, e);
        System.out.println(mav.getModel());
        System.out.println(mav.getView());
    }


    static class Controller2 {
        public void foo() {

        }

        @ExceptionHandler
        public ModelAndView handle(ArithmeticException e) {
            return new ModelAndView("test2", Map.of("error", e.getMessage()));
        }
    }
}

测试结果如下:

18:45:14.885 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Using @ExceptionHandler com.cherry.a30.A30$Controller2#handle(ArithmeticException)
18:45:14.890 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Resolved [java.lang.ArithmeticException: 被零除] to ModelAndView [view="test2"; model={error=被零除}]
{error=被零除}
null

我们发现,在执行过程中出现了差错就会将出现的异常数据放到ModelAndView中的Model中,当然,view也会存放异常处理器指定的视图名称!

3. 嵌套异常

接下来我们看一下比较有意思的情况,就是异常嵌套的情况,其实这种情况也是非常的常见,例如下面的代码:

public class A30 {
    public static void main(String[] args) throws NoSuchMethodException {
        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
        // 添加必要的参数解析器和返回值处理器
        // 添加一个消息转换器:JAVA<->JSON
        resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
        // 添加一个参数解析器和返回值处理器
        resolver.afterPropertiesSet();
        // 测试JSON
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();

        HandlerMethod handlerMethod = new HandlerMethod(new Controller3(), Controller3.class.getDeclaredMethod("foo"));
        Exception e = new Exception("Exception",new RuntimeException("Runtime Exception", new IOException("IO Exception")));
        resolver.resolveException(request, response, handlerMethod, e);
        System.out.println(new String(response.getContentAsByteArray(),StandardCharsets.UTF_8));
    }


    static class Controller3 {
        public void foo() {

        }

        @ExceptionHandler
        @ResponseBody
        public Map<String, Object> handle(IOException e3) {
            return Map.of("error", e3.getMessage());
        }
    }
}

我们在代码中创建了一个嵌套异常,分别是Exception, Runtime Exception, IO Exception,而是Controller中的@ExceptionHandler注解所标注的方法上传递的参数时是IO异常,最终的打印是什么异常呢?我们可以来看一下输出结果:

18:51:35.598 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Using @ExceptionHandler com.cherry.a30.A30$Controller3#handle(IOException)
18:51:35.659 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Using 'application/json', given [*/*] and supported [application/json, application/*+json]
18:51:35.661 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Writing [{error=IO Exception}]
18:51:35.698 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Resolved [java.lang.Exception: Exception]
{"error":"IO Exception"}

我们发现,最终打印的是IO Exception。这是为什么呢?原来,是在反射调用@ExceptionHandler注解标注的异常处理方法之前,它会将handlerMethod中所有的异常全部拿过来,从最外层开始拿到异常,并将该异常添加到一个集合中,并不断获取其内部嵌套的异常也加入到异常中,直到最内层的异常也加入到存放异常的集合中。然后将这些异常与@ExceptionHandler注解标注的异常处理方法中的异常参数进行匹配,如果匹配成功,则会由返回值处理器和消息转换器将该异常信息放到ModelAndView的Model中。

这就是处理嵌套异常的原理!

posted @   LilyFlower  阅读(13)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示