0%

HandlerExceptionResolver

Spring MVC HandlerExceptionResolver 详解:异常处理的核心机制与实战

HandlerExceptionResolver 是 Spring MVC 中统一异常处理的核心接口,负责在视图渲染(render)前拦截 Controller 或 Handler 抛出的异常,通过解析异常生成 ModelAndView(用于视图渲染)或直接处理响应(如设置 HTTP 状态码、返回 JSON 错误信息),避免异常直接抛给用户,提升系统容错性和用户体验。从 “核心接口→实现类对比→ExceptionHandlerExceptionResolver 源码解析→实战场景” 四个维度,彻底讲透 HandlerExceptionResolver 的工作原理。

HandlerExceptionResolver 核心定义与设计目标

1. 核心接口方法

HandlerExceptionResolver 接口仅定义一个核心方法,所有实现类需通过该方法完成异常解析:

1
2
3
4
5
6
7
8
9
10
11
12
public interface HandlerExceptionResolver {
/**
* 解析异常,返回 ModelAndView 或直接处理响应
* @param request 当前 HTTP 请求
* @param response 当前 HTTP 响应
* @param handler 抛出异常的处理器(如 Controller 实例、HandlerMethod)
* @param ex 待处理的异常
* @return ModelAndView:包含错误视图和模型数据(需渲染);null:异常已直接处理(如设置响应码、返回 JSON)
*/
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}
关键逻辑:
  • 若返回 ModelAndView:DispatcherServlet 会继续渲染视图(如错误页面);
  • 若返回 null:表示异常已被 “直接处理”(如通过 response.sendError() 设置状态码,或通过 @ResponseBody 返回 JSON 错误信息);
  • 执行时机:在 Controller 方法执行后、视图渲染(render)前,属于 “异常处理的最后一道防线”。

2. 设计目标

  • 集中化异常处理:替代 try-catch 代码块,将分散在 Controller、Service 层的异常统一拦截处理;
  • 解耦异常处理与业务逻辑:业务代码无需关注异常处理细节,只需抛出异常,由 HandlerExceptionResolver 统一接管;
  • 灵活适配场景:支持不同异常处理策略(如视图渲染、JSON 响应、HTTP 状态码设置)。

HandlerExceptionResolver 四大核心实现类

Spring MVC 提供 4 个常用实现类,覆盖从 “标准异常映射” 到 “注解驱动” 的所有场景,以下是核心对比:

实现类 核心逻辑 适用场景 关键特性
DefaultHandlerExceptionResolver 将 Spring 标准异常(如 HttpRequestMethodNotSupportedException)映射为 HTTP 状态码(如 405 Method Not Allowed) 处理框架级异常(请求方法不支持、参数绑定失败等) 仅设置响应码,不修改响应体;无需配置,默认生效
ResponseStatusExceptionResolver 解析带有 @ResponseStatus 注解的自定义异常,将注解的 code 作为 HTTP 状态码 自定义业务异常需映射到特定状态码的场景(如 400 非法参数) 依赖 @ResponseStatus 注解;仅设置状态码和简单原因
SimpleMappingExceptionResolver 通过 XML/Java 配置 “异常类→视图名” 的映射,直接返回错误视图 传统视图驱动项目(如 JSP 错误页面) 配置简单,支持异常继承关系匹配(如 Exception 匹配所有异常)
ExceptionHandlerExceptionResolver 解析标注 @ExceptionHandler 的方法(局部:Controller 内;全局:@ControllerAdvice 内),执行方法处理异常 注解驱动项目(主流),支持复杂异常处理(如返回 JSON、自定义响应体) 支持参数注入(如 ExceptionHttpServletRequest)、返回值灵活(视图名、JSON)

各实现类执行优先级

当容器中存在多个 HandlerExceptionResolver 时,DispatcherServlet 按以下顺序调用(优先级从高到低):

  1. ExceptionHandlerExceptionResolvermvc:annotation-driven 自动注册,优先级最高);
  2. ResponseStatusExceptionResolver(默认注册,处理 @ResponseStatus 异常);
  3. DefaultHandlerExceptionResolver(默认注册,处理框架标准异常);
  4. SimpleMappingExceptionResolver(需手动配置,优先级最低)。

核心实现类解析:ExceptionHandlerExceptionResolver

ExceptionHandlerExceptionResolver 是当前注解驱动项目的首选异常处理器,负责解析 @ExceptionHandler 注解的方法(局部 Controller 内或全局 @ControllerAdvice 内),支持复杂的异常处理逻辑(如参数注入、JSON 响应)。拆解其核心流程。

1. 父类模板方法:AbstractHandlerExceptionResolver#resolveException

ExceptionHandlerExceptionResolver 继承 AbstractHandlerExceptionResolver(抽象类),父类提供 “模板方法”resolveException,定义异常处理的通用流程,子类仅需实现具体解析逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// AbstractHandlerExceptionResolver#resolveException(核心模板方法)
@Override
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 步骤1:判断当前处理器是否适用当前异常(子类可重写 shouldApplyTo)
if (shouldApplyTo(request, handler)) {
// 步骤2:准备响应(禁用缓存,避免错误页面被缓存)
prepareResponse(ex, response);
// 步骤3:子类实现的具体异常解析逻辑(核心)
ModelAndView result = doResolveException(request, response, handler, ex);
// 步骤4:日志记录(可选)
if (result != null) {
logException(ex, request);
}
return result;
} else {
// 不适用当前处理器,返回 null,交给下一个 HandlerExceptionResolver
return null;
}
}
关键步骤解析:
  • shouldApplyTo:判断当前异常是否应由该处理器处理(如仅处理特定 Controller 的异常);
  • prepareResponse:设置响应头 Cache-Control: no-store,禁用缓存,确保错误页面实时更新;
  • doResolveException:子类核心实现,ExceptionHandlerExceptionResolver 重写该方法,实现 @ExceptionHandler 方法的查找与执行。

2. 适用判断:shouldApplyTo 方法

shouldApplyTo 方法决定 “当前异常是否应由该处理器处理”,核心逻辑是 “匹配处理器(handler)是否在配置的范围内”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// AbstractHandlerMethodExceptionResolver#shouldApplyTo(ExceptionHandlerExceptionResolver 的父类)
@Override
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
if (handler == null) {
return super.shouldApplyTo(request, handler);
} else if (handler instanceof HandlerMethod) {
// 若 handler 是 HandlerMethod(注解驱动的方法),取其所属的 Bean(Controller 实例)
HandlerMethod handlerMethod = (HandlerMethod) handler;
handler = handlerMethod.getBean();
return super.shouldApplyTo(request, handler);
} else {
// 非 HandlerMethod 类型(如传统 Controller 接口),不处理
return false;
}
}

// AbstractHandlerExceptionResolver#shouldApplyTo(最终判断逻辑)
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
if (handler != null) {
// 1. 若配置了 mappedHandlers(特定处理器集合),判断 handler 是否在集合中
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
return true;
}
// 2. 若配置了 mappedHandlerClasses(特定处理器类型集合),判断 handler 是否为该类型实例
if (this.mappedHandlerClasses != null) {
for (Class<?> handlerClass : this.mappedHandlerClasses) {
if (handlerClass.isInstance(handler)) {
return true;
}
}
}
}
// 3. 若未配置 mappedHandlers/mappedHandlerClasses,默认处理所有异常
return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}
配置场景示例:

通过 setMappedHandlerssetMappedHandlerClasses,限制处理器仅处理特定 Controller 的异常:

1
2
3
4
5
6
7
8
9
10
@Configuration
public class ExceptionConfig {
@Bean
public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
// 仅处理 UserController 的异常
resolver.setMappedHandlerClasses(UserController.class);
return resolver;
}
}

3. 核心解析逻辑:doResolveException 方法

ExceptionHandlerExceptionResolver 重写 doResolveException,核心是 “找到 @ExceptionHandler 方法→执行方法→处理返回值”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// ExceptionHandlerExceptionResolver#doResolveException
protected final ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 仅处理 HandlerMethod 类型的处理器(注解驱动的方法)
return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
}

// 核心:处理 HandlerMethod 抛出的异常
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {
// 步骤1:找到匹配的 @ExceptionHandler 方法(局部 Controller 或全局 @ControllerAdvice)
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
// 无匹配的异常处理方法,返回 null,交给下一个 HandlerExceptionResolver
return null;
}

// 步骤2:配置异常处理方法的参数解析器和返回值处理器
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);

// 步骤3:封装请求/响应和模型容器
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();

try {
// 步骤4:处理异常链(若有 cause,传递 cause 作为参数)
Throwable cause = exception.getCause();
if (cause != null) {
// 执行 @ExceptionHandler 方法,传递 exception、cause、handlerMethod 等参数
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
} else {
// 无 cause,仅传递 exception
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
}
} catch (Throwable invocationEx) {
// 异常处理方法自身抛出异常,返回 null,交给下一个处理器
return null;
}

// 步骤5:处理返回结果
if (mavContainer.isRequestHandled()) {
// 标记为“请求已处理”(如 @ResponseBody 返回 JSON),返回空 ModelAndView
return new ModelAndView();
} else {
// 构建 ModelAndView(包含视图名、模型数据、HTTP 状态码)
ModelMap model = mavContainer.getModel();
HttpStatus status = mavContainer.getStatus();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
mav.setViewName(mavContainer.getViewName());
// 处理重定向参数(FlashMap)
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
}
关键步骤拆解:
(1)getExceptionHandlerMethod:查找 @ExceptionHandler 方法

这是最核心的步骤,查找逻辑优先级为:

  1. 局部优先:先查找当前 Controller 中标注 @ExceptionHandler 且异常类型匹配的方法;
  2. 全局兜底:若局部无匹配方法,查找 @ControllerAdvice 标注的类中符合条件的 @ExceptionHandler 方法;
  3. 异常类型匹配:方法参数中的异常类型需与抛出的异常类型一致或为其父类(如 Exception 匹配所有异常)。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 局部异常处理(UserController 内)
@Controller
public class UserController {
// 处理当前 Controller 抛出的 BusinessException
@ExceptionHandler(BusinessException.class)
public ModelAndView handleBusinessException(BusinessException e) {
ModelAndView mav = new ModelAndView("error/business");
mav.addObject("msg", e.getMessage());
return mav;
}
}

// 全局异常处理(@ControllerAdvice)
@ControllerAdvice
public class GlobalExceptionHandler {
// 处理所有 Controller 抛出的 Exception(兜底)
@ExceptionHandler(Exception.class)
@ResponseBody
public Map<String, Object> handleGlobalException(Exception e) {
Map<String, Object> result = new HashMap<>();
result.put("code", 500);
result.put("msg", "服务器内部错误:" + e.getMessage());
return result;
}
}
(2)invokeAndHandle:执行 @ExceptionHandler 方法

通过反射调用找到的 @ExceptionHandler 方法,支持灵活的参数注入(如 ExceptionHttpServletRequestModel)和返回值类型(如 ModelAndViewString 视图名、@ResponseBody 标注的 JSON 对象)。

示例:支持多参数的异常处理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@ExceptionHandler(BusinessException.class)
@ResponseBody
public ResponseEntity<ErrorResult> handleBusinessException(
BusinessException e, // 抛出的异常
HttpServletRequest request, // 当前请求
HttpServletResponse response) { // 当前响应
ErrorResult error = new ErrorResult(400, e.getMessage());
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}

// 自定义错误响应类
@Data
class ErrorResult {
private int code;
private String msg;
public ErrorResult(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
(3)结果处理:ModelAndView 或直接响应
  • mavContainer.isRequestHandled() = true:表示异常已通过 @ResponseBody 直接返回响应(无视图渲染),返回空 ModelAndView
  • 若为 false:构建包含视图名、模型数据的 ModelAndView,DispatcherServlet 继续渲染错误视图(如 JSP 错误页面)。

其他实现类快速解析

1. DefaultHandlerExceptionResolver(框架标准异常处理)

核心是 “Spring 标准异常→HTTP 状态码” 的映射,无需配置,默认生效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 示例:处理 HttpRequestMethodNotSupportedException(请求方法不支持)
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try {
if (ex instanceof HttpRequestMethodNotSupportedException) {
// 设置 HTTP 405 状态码(Method Not Allowed)
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
return new ModelAndView(); // 标记为已处理
} else if (ex instanceof HttpMediaTypeNotSupportedException) {
// 设置 HTTP 415 状态码(Unsupported Media Type)
response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, ex.getMessage());
return new ModelAndView();
}
// ... 其他标准异常映射
} catch (Exception handlerEx) {
logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerEx);
}
return null;
}

2. ResponseStatusExceptionResolver(@ResponseStatus 异常处理)

解析带有 @ResponseStatus 的自定义异常,将注解的 code 作为 HTTP 状态码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 自定义异常(带 @ResponseStatus)
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "非法参数")
public class InvalidParamException extends RuntimeException {
public InvalidParamException(String message) {
super(message);
}
}

// ResponseStatusExceptionResolver 核心逻辑
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
if (responseStatus != null) {
try {
// 设置 HTTP 状态码和原因
response.sendError(responseStatus.code().value(), responseStatus.reason());
return new ModelAndView();
} catch (Exception e) {
logger.error("Failed to send error response", e);
}
}
return null;
}

3. SimpleMappingExceptionResolver(配置异常→视图映射)

通过配置 “异常类名→视图名”,直接返回错误视图,适合传统 JSP 项目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- XML 配置示例 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 默认错误视图(所有未匹配异常) -->
<property name="defaultErrorView" value="error/default"/>
<!-- 异常→视图映射(key:异常全类名,value:视图名) -->
<property name="exceptionMappings">
<props>
<prop key="com.example.BusinessException">error/business</prop>
<prop key="java.lang.NullPointerException">error/null</prop>
</props>
</property>
<!-- 暴露异常对象到视图(属性名:exception) -->
<property name="exposeExceptionAttributes" value="true"/>
</bean>

实战:ExceptionHandlerExceptionResolver 最佳实践

1. 局部 vs 全局异常处理优先级

  • 局部处理:Controller 内的 @ExceptionHandler 方法优先于全局 @ControllerAdvice 方法;
  • 异常类型精确匹配优先:若局部方法匹配 “具体异常”(如 BusinessException),全局方法匹配 “父异常”(如 Exception),则局部方法生效。

2. 返回 JSON 格式错误响应(前后端分离)

结合 @ResponseBodyResponseEntity,返回标准化 JSON 错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@ControllerAdvice
public class GlobalJsonExceptionHandler {
// 处理业务异常(返回 JSON)
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResult> handleBusinessException(BusinessException e) {
ErrorResult error = new ErrorResult(400, e.getMessage());
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}

// 处理系统异常(兜底)
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResult> handleSystemException(Exception e) {
ErrorResult error = new ErrorResult(500, "服务器内部错误");
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}

// 标准化错误响应类
@Data
@NoArgsConstructor
@AllArgsConstructor
class ErrorResult {
private int code; // 业务错误码
private String msg; // 错误信息
private long timestamp = System.currentTimeMillis(); // 时间戳
}

3. 自定义参数解析器(扩展异常处理方法参数)

若需在 @ExceptionHandler 方法中注入自定义参数(如当前登录用户),可扩展 HandlerMethodArgumentResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 自定义参数解析器:注入当前登录用户
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CurrentUser.class) && parameter.getParameterType() == User.class;
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 从 Session 或 Token 中获取当前用户
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
return request.getSession().getAttribute("loginUser");
}
}

// 自定义注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {}

// 在异常处理方法中使用
@ExceptionHandler(BusinessException.class)
@ResponseBody
public ErrorResult handleException(
BusinessException e,
@CurrentUser User loginUser) { // 注入当前登录用户
log.error("用户 {} 操作异常:{}", loginUser.getId(), e.getMessage());
return new ErrorResult(400, e.getMessage());
}

// 配置参数解析器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new CurrentUserArgumentResolver());
}
}

常见问题与解决方案

1. @ExceptionHandler 方法不生效

  • 原因 1:异常类型不匹配(如方法参数是 BusinessException,但抛出的是 NullPointerException);
  • 原因 2:方法参数错误(如缺少 Exception 参数,或参数类型无法被解析器识别);
  • 原因 3:全局 @ControllerAdvice 未被扫描(如包路径未包含在 @ComponentScan 范围内);
  • 解决方案:
    1. 确保异常类型与方法参数一致;
    2. 检查方法参数是否支持(可注入 ExceptionHttpServletRequestModel 等);
    3. 确认 @ControllerAdvice 所在包被 @ComponentScan 扫描。

2. 异常处理方法抛出异常

  • 现象@ExceptionHandler 方法自身抛出异常,导致用户看到 500 错误;
  • 原因:异常处理方法未捕获自身逻辑的异常;
  • 解决方案:在异常处理方法中添加 try-catch,或确保逻辑无异常(如避免空指针)。

3. 前后端分离场景下返回视图而非 JSON

  • 原因@ExceptionHandler 方法未添加 @ResponseBody 注解,返回值被解析为视图名;
  • 解决方案:添加 @ResponseBody 注解,或使用 @RestControllerAdvice@ControllerAdvice + @ResponseBody)。

总结

HandlerExceptionResolver 是 Spring MVC 异常处理的核心体系,其中:

  • ExceptionHandlerExceptionResolver:注解驱动的首选方案,支持局部 / 全局异常处理,灵活适配 JSON 响应和视图渲染;
  • DefaultHandlerExceptionResolver:默认处理框架标准异常,无需配置;
  • ResponseStatusExceptionResolver:简化自定义异常到 HTTP 状态码的映射;
  • SimpleMappingExceptionResolver:适合传统视图项目,配置异常到视图的映射。

在实际开发中,推荐以 ExceptionHandlerExceptionResolver 为核心(通过 @ControllerAdvice + @ExceptionHandler),结合 ResponseStatusExceptionResolver 处理自定义异常的状态码,实现 “集中化、可扩展” 的异常处理机制,减少业务代码中的 try-catch 冗余,提升系统可维护性

欢迎关注我的其它发布渠道