Skip to content

异常处理 (Exceptions)

如果在请求映射过程中发生异常,或者从请求处理器(如 @Controller)中抛出异常,DispatcherServlet 会委托给一个 HandlerExceptionResolver 链来处理异常并提供替代方案(通常是错误响应)。

可用的解析器实现

下表列出了常用的 HandlerExceptionResolver

解析器实现说明
ExceptionHandlerExceptionResolver最常用。用于调用 @Controller@ControllerAdvice 类中的 @ExceptionHandler 方法。
ResponseStatusExceptionResolver根据异常上的 @ResponseStatus 注解将其映射到 HTTP 状态码。
DefaultHandlerExceptionResolver解析 Spring MVC 产生的标准异常(如参数绑定失败),并将其映射到对应的 HTTP 状态码。
SimpleMappingExceptionResolver允许在异常类名和错误视图名之间建立简单的映射关系(适用于传统 JSP/Thymeleaf 页面跳转)。

解析器链 (Chain of Resolvers)

你可以通过在配置中声明多个 HandlerExceptionResolver Bean 并设置其 order 属性来形成一条链。order 值越小,优先级越高。

解析器的处理逻辑通常遵循以下契约:

  1. 返回一个指向错误视图的 ModelAndView
  2. 返回一个空的 ModelAndView(表示异常已处理)。
  3. 返回 null(表示无法处理,交给链中的下一个解析器)。

容器错误页面 (Container Error Page)

如果所有解析器都无法处理异常,或者响应状态码被显式设为错误(4xx, 5xx),Servlet 容器可以渲染默认的 HTML 错误页面。你可以通过在 web.xml 中配置 <error-page> 来自定义:

xml
<error-page>
    <location>/error</location>
</error-page>

在这种情况下,容器会向 /error 发起内部请求,由 DispatcherServlet 将其映射到一个控制器进行自定义渲染:

java
@RestController
public class ErrorController {
    @RequestMapping(path = "/error")
    public Map<String, Object> handle(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
        map.put("status", request.getAttribute("jakarta.servlet.error.status_code"));
        map.put("reason", request.getAttribute("jakarta.servlet.error.message"));
        return map;
    }
}
kotlin
@RestController
class ErrorController {
    @RequestMapping(path = ["/error"])
    fun handle(request: HttpServletRequest): Map<String, Any> {
        val map = HashMap<String, Any>()
        map["status"] = request.getAttribute("jakarta.servlet.error.status_code")
        map["reason"] = request.getAttribute("jakarta.servlet.error.message")
        return map
    }
}

补充教学

1. 为什么推荐使用 @ControllerAdvice?

早期异常处理散落在各个 Controller 中(使用 @ExceptionHandler)。 现代推荐:创建一个标注了 @ControllerAdvice 的全局异常处理类。

  • 解耦:业务代码里只管抛出异常(如 UserNotFoundException),不需要关心渲染。
  • 统一:全局异常类负责捕获特定异常,并统一转换成标准的 REST 错误格式(如包含 timestampmsg 的 JSON)。

2. Spring Boot 里的 /error 是怎么来的?

在 Spring Boot 中,你并不需要配置 web.xml 或手动写 ErrorController

  • BasicErrorController:Spring Boot 自动为你配置了一个 BasicErrorController
  • 策略:它会根据请求头的 Accept 自动判断。如果是浏览器请求,返回 HTML 错误页面(你可以通过在 templates/error/ 下创建 404.html 自定义);如果是代码(JSON)请求,返回标准的错误 JSON。

3. 理解 ExceptionResolver 的吞没 (Swallow) 行为

请记住:一旦某个解析器处理了异常并返回了非空 ModelAndView,该异常就被认为“处理完成”了,之后不会再往外抛给 Tomcat 或 Spring Security。这在进行全局审计或异常埋点时需要额外留意。

Based on Spring Framework.