解读Spring Boot框架中不同位置抛出异常的处理流程

背景知识

对于Java Web开发而言,客户端发起的HTTP请求处理顺序为:Servlet容器 -> Filter -> Servlet -> Interceptor -> Controller,参考:Spring拦截器HandlerInterceptor与Filter方法执行顺序探究
如下是一个客户端HTTP请求到达Serlvet容器后的完整执行流程:

-> org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun()                             -------
  -> org.apache.coyote.AbstractProtocol$ConnectionHandler.process()                              |
    -> org.apache.coyote.AbstractProcessorLight.process()                                        |
      -> org.apache.coyote.http11.Http11Processor.service()                                      |
        -> org.apache.catalina.connector.CoyoteAdapter.service()                                 |
          -> org.apache.catalina.core.StandardEngineValve.invoke()                           Servlet容器
            -> org.apache.catalina.valves.ErrorReportValve.invoke()                              |
              -> org.apache.catalina.core.StandardHostValve.invoke()                             |
                -> org.apache.catalina.authenticator.AuthenticatorBase.invoke()                  |
                  -> org.apache.catalina.core.StandardContextValve.invoke()                      |
                    -> org.apache.catalina.core.StandardWrapperValve.invoke()                    |
                      -> org.apache.catalina.core.ApplicationFilterChain.doFilter()              |
                        -> org.apache.catalina.core.ApplicationFilterChain.internalDoFilter()  -------
                          -> 执行Filter.doFilter():如果在这里抛出了异常,在StandardWrapperValve.invoke()方法中会被捕获,并将异常对象保存到request属性中,返回到StandardHostValve.invoke()方法后检查request属性中的异常对象是否为空,如果不为空就执行RequestDispatcher.formard()访问/error路径;如果在Filter中未抛出异常,继续执行Servlet -> Interceptor -> Controller
                            -> javax.servlet.http.HttpServlet.service()                                   
                              -> org.springframework.web.servlet.FrameworkServlet.service()                  
                                -> org.springframework.web.servlet.FrameworkServlet.doGet()等                 
                                  -> org.springframework.web.servlet.FrameworkServlet.processRequest()        
                                    -> org.springframework.web.servlet.DispatcherServlet.processRequest()  
                                      -> org.springframework.web.servlet.DispatcherServlet.doService()        
                                        -> org.springframework.web.servlet.DispatcherServlet.doDispatch()     
                                          -> org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle()
                                            -> 执行HandlerInterceptor.preHandle()                                    
                                              -> org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle()
                                                -> org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal()
                                                  -> org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod()
                                                    -> org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle()
                                                      -> org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest()
                                                        -> org.springframework.web.method.support.InvocableHandlerMethod.doInvoke()
                                                          -> 执行Controller方法
                                                        -> org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue()
                                                          -> org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue()
                                                            -> org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters()
                                                              -> org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyAdviceChain.beforeBodyWrite()
                                                                -> org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice.beforeBodyWrite()
                                                          <-
                                                        <-
                                                    <-
                                                  -> org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.getModelAndView()
                                                <-
                                              <-
                                          :如果在HandlerInterceptor.preHandle()方法或者Controller方法中抛出异常,会在DispatcherServlet.doDispatch()方法中被捕获,进入到DispatcherServlet.processDispatchResult()方法,最后调用全局异常拦截器进行处理
                                            -> org.springframework.web.servlet.DispatcherServlet.applyDefaultViewName()
                                            -> 执行HandlerInterceptor.postHandle()
                                            -> 执行HandlerInterceptor.afterCompletion()
                                  <-
                          <-
                    <-
                  <-
                <-
              <-
            <-
          <-
        <-
      <-
    <-
  <-
<- 将响应数据写回客户端

在Filter中抛出异常

客户端发起的HTTP请求最先到达的组件就是Filter,如果在Filter.doFilter()方法中抛出了异常,在StandardWrapperValve.invoke()方法中会被捕获,并将异常对象保存到request属性中,返回到StandardHostValve.invoke()方法后检查request属性中的异常对象是否为空,如果不为空就执行RequestDispatcher.formard()访问/error路径。

// org.apache.catalina.core.StandardWrapperValve

private void exception(Request request, Response response,
                        Throwable exception) {
    // 将在Filter.doFilter()方法中抛出的异常对象保存到request属性中,属性名为RequestDispatcher.ERROR_EXCEPTION,即:"javax.servlet.error.exception"
    request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, exception);
    // 设置HTTP响应状态码
    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    // 设置错误状态标志
    response.setError();
}
// org.apache.catalina.core.StandardHostValve

public final void invoke(Request request, Response response)
    throws IOException, ServletException {

    // 省略其他代码...

    try {
        // 省略其他代码...

        // 从request属性中获取异常对象
        Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

        // Protect against NPEs if the context was destroyed during a
        // long running request.
        if (!context.getState().isAvailable()) {
            return;
        }

        // Look for (and render if found) an application level error page
        if (response.isErrorReportRequired()) {
            if (t != null) {
                // 如果异常不为空,
                throwable(request, response, t);
            } else {
                status(request, response);
            }
        }

        // 省略其他代码...

    } finally {
        // 省略其他代码...
    }
}

protected void throwable(Request request, Response response,
                             Throwable throwable) {
    // 省略其他代码...

    status(request, response);
    
    // 省略其他代码...
}

// 在custom()方法中调用RequestDispatcher.forward()访问/error路径
private boolean custom(Request request, Response response,
                            ErrorPage errorPage) {

    if (container.getLogger().isDebugEnabled()) {
        container.getLogger().debug("Processing " + errorPage);
    }

    try {
        // Forward control to the specified location
        ServletContext servletContext =
            request.getContext().getServletContext();
        RequestDispatcher rd =
            servletContext.getRequestDispatcher(errorPage.getLocation());

        if (rd == null) {
            container.getLogger().error(
                sm.getString("standardHostValue.customStatusFailed", errorPage.getLocation()));
            return false;
        }

        if (response.isCommitted()) {
            // Response is committed - including the error page is the
            // best we can do
            rd.include(request.getRequest(), response.getResponse());
        } else {
            // Reset the response (keeping the real error code and message)
            response.resetBuffer(true);
            response.setContentLength(-1);

            // 执行RequestDispatcher.forward()方法访问错误页面(默认为/error)
            rd.forward(request.getRequest(), response.getResponse());

            // If we forward, the response is suspended again
            response.setSuspended(false);
        }

        // Indicate that we have successfully processed this custom page
        return true;

    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        // Report our failure to process this custom page
        container.getLogger().error("Exception Processing " + errorPage, t);
        return false;

    }
}

正因为如此,在Filter中抛出的异常无法被全局异常拦截器处理,如果希望在全局异常拦截器中处理Filter中抛出的异常,需要自定义org.springframework.boot.autoconfigure.web.ErrorController实现。

// 自定义org.springframework.boot.autoconfigure.web.ErrorController实现,将在Filter中抛出的异常交给全局异常拦截器处理

@RequestMapping("/error")
public class DefaultErrorController implements ErrorController {
    @RequestMapping
    public void handleError(HttpServletRequest req) throws Throwable {
        Object exceptAttr = req.getAttribute("javax.servlet.error.exception");
        if (exceptAttr != null) {
            // 将异常信息抛给全局异常拦截器处理
            throw (Throwable) exceptAttr;
        }
    }
}

在HandlerInterceptor或Controller中抛出异常

如果在HandlerInterceptor.preHandle()方法或者Controller方法中抛出异常,会在DispatcherServlet.doDispatch()方法中被捕获,进入到DispatcherServlet.processDispatchResult()方法,最后调用全局异常拦截器进行处理。

// org.springframework.web.servlet.DispatcherServlet

// 省略其他代码...

/**
 * Handle the result of handler selection and handler invocation, which is
 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
 */
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
        HandlerExecutionChain mappedHandler, ModelAndView mv, 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);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // 省略其他代码...

    if (mappedHandler != null) {
        // 不论HandlerInterceptor.preHandle()方法或Controller方法是否抛出异常,HandlerInterceptor.afterCompletion()方法总是会被调用
        // 特别注意:如果HandlerInterceptor.preHandle()方法未抛出异常,而是明确返回了false,HandlerInterceptor.afterCompletion()方法不会被调用
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

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

    // Check registered HandlerExceptionResolvers...
    ModelAndView exMv = null;
    // 在这里通过全局异常拦截器处理在Controller或HandlerInterceptor.preHandle()方法中抛出的异常
    for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
        exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
        if (exMv != null) {
            break;
        }
    }
    
    // 省略其他代码...
}

解读完毕。

posted @ 2025-11-27 20:53  nuccch  阅读(0)  评论(0)    收藏  举报