惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Know Your Adversary
Know Your Adversary
博客园 - 叶小钗
量子位
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 【当耐特】
博客园 - Franky
有赞技术团队
有赞技术团队
博客园 - 聂微东
腾讯CDC
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Jina AI
Jina AI
Last Week in AI
Last Week in AI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Cloudbric
Cloudbric
WordPress大学
WordPress大学
W
WeLiveSecurity
V2EX - 技术
V2EX - 技术
博客园_首页
S
Security @ Cisco Blogs
The Last Watchdog
The Last Watchdog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Security Latest
Security Latest
L
Lohrmann on Cybersecurity
T
Threat Research - Cisco Blogs
Forbes - Security
Forbes - Security
宝玉的分享
宝玉的分享
The Register - Security
The Register - Security
The Hacker News
The Hacker News
B
Blog RSS Feed
C
CXSECURITY Database RSS Feed - CXSecurity.com
Schneier on Security
Schneier on Security
T
Troy Hunt's Blog
The GitHub Blog
The GitHub Blog
Hacker News: Ask HN
Hacker News: Ask HN
Spread Privacy
Spread Privacy
Hugging Face - Blog
Hugging Face - Blog
博客园 - 三生石上(FineUI控件)
GbyAI
GbyAI
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
罗磊的独立博客
Blog — PlanetScale
Blog — PlanetScale
M
MIT News - Artificial intelligence
T
Tor Project blog
S
Security Affairs
Security Archives - TechRepublic
Security Archives - TechRepublic
NISL@THU
NISL@THU
P
Proofpoint News Feed
C
Cyber Attacks, Cyber Crime and Cyber Security

博客园 - nuccch

在Cursor中读取飞书文档 使用GIMP去除水印的有效方法 如何基于VSCode打造Java开发环境 在IDEA中配置注释模板 在Windows中使用Linux系统 树形层级结构的数据库表设计方案 如何理解和认识设计模式 申请Let's Encrypt免费HTTPS证书的方法 为GIT仓库项目设置独立配置参数 DBeaver设置不断开连接 构建工具Gradle入门实践 如何在Maven中排除依赖传递 87键键盘的数字键对应快捷键含义 关于Java JSON库的选择 解决mybatis批量更新慢问题 解决Spring Cloud Gateway中使用CompletableFuture.supplyAsync()执行Feign调用报错 Spring Boot框架中在Controller方法里获取Request和Response对象的2种方式 探究Spring Boot框架中访问不存在的接口时触发对error路径的访问 Spring Cloud工程中使用Nacos配置中心的2种方式 Swagger开启账号验证访问
解读Spring Boot框架中不同位置抛出异常的处理流程
nuccch · 2025-11-27 · via 博客园 - nuccch

背景知识

对于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;
        }
    }
    
    // 省略其他代码...
}

解读完毕。