Spring Web : 注解@ExceptionHandler的工作原理

我们知道,Spring Web注解@ExceptionHandler可以用来指定处理某类异常的控制器方法,从而在这些异常发生时,会有相应的控制器方法来处理此类异常,其定义方式如下 :

    /**
     * 此方法定义一个异常处理器,仅仅处理异常 DemoException , 它使用一个视图对象 
     * DemoExceptionHandlerView 来处理异常 
     * @param e
     * @return
     */
    @ExceptionHandler(DemoException.class)
    public View handleDemoException(DemoException e) {
        return new DemoExceptionHandlerView(e);
    }

另外,通过@ExceptionHandler定义异常处理控制器方法,又可以分为两类 :

  1. 定义在某个控制类内
    这种情况下,所定义的异常处理控制器方法仅仅覆盖当前控制器类内各个方法所发生的这类异常
  2. 结合@ControllerAdvice使用,定义在@ControllerAdvice注解的类内
    这种情况下,所定义的异常处理控制器方法可用于@ControllerAdvice注解覆盖的所有控制器类方法内所发生的这类异常

那么,在这种现象背后,Spring MVC又是如何实现的呢 ? 实际上,这主要是 ExceptionHandlerExceptionResolver在起作用 :

  1. 首先,ExceptionHandlerExceptionResolverSpring MVC缺省被启用的一个HandlerExceptionResolver,它会被作为一个组合模式HandlerExceptionResolver bean中的一个元素进入到bean容器中。

    关于该知识点,可以参考:
    Spring MVC : WebMvcConfigurationSupport 中定义的 HandlerExceptionResolver 组件

ExceptionHandlerExceptionResolver实现了接口InitializingBean,所以它在实例化时会被初始化。该过程中,它就会搜集所有的@ControllerAdvice注解类中使用@ExceptionHandler定义的异常处理控制器方法以供随后工作时使用。

  1. 接下来,DispatcherServlet初始化时,会搜集所有HandlerExceptionResolver bean记录到自己的策略组件属性List<HandlerExceptionResolver> handlerExceptionResolvers

    关于这一知识点,可以参考 :
    Spring MVC DispatcherServlet 策略初始化 – initHandlerExceptionResolvers

  2. 然后,处理某个请求时某个异常发生了。DispatcherServlet会遍历handlerExceptionResolvers中每个HandlerExceptionResolver对象试图对该异常进行处理。

    // DispatcherServlet 代码片段,
    // 在 HandlerAdaptor 执行 Handler 之后调用,
    // 如果之前的逻辑有异常,则 exception 不为 null,
    // 如果之前的逻辑执行正常, 则 exception 为 null
	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);
              // 这里视图处理该异常:
              // 1. 内部将其完全处理
              // 2. 或者返回一个用于渲染错误的数据模型和视图
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}
        
        // ... 省略其他代码
    }        
	@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) {
          // 遍历`handlerExceptionResolvers`中每个`HandlerExceptionResolver`对象
          // 试图对异常 ex 进行处理
			for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
				exMv = resolver.resolveException(request, response, handler, ex);
				if (exMv != null) {
					break;
				}
			}
		}
		if (exMv != null) {
			// ...
			return exMv;
		}

		throw ex;
	}    

这里,轮到ExceptionHandlerExceptionResolver处理异常时,#resolveException最终会调用ExceptionHandlerExceptionResolver#doResolveHandlerMethodException。该处理过程主要步骤如下 :

  1. 找到能处理该异常的控制器方法 – ExceptionHandlerExceptionResolver#getExceptionHandlerMethod;
    1. 先从发生异常的控制器方法所在类查找是否存在使用注解@ExceptionHandler并能处理该异常的方法;
    2. 如果找不到,从所有@ControllerAdvice注解类中查找使用注解@ExceptionHandler并能处理该异常的方法;
  2. 执行处理该异常的控制器方法处理该异常;

更详细的源码分析,这里就不再展开了,可以参考下面这篇文章 :
Spring MVC : 工具 ExceptionHandlerExceptionResolver

参考文章

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值