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 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、自定义响应体) |
支持参数注入(如 Exception、HttpServletRequest)、返回值灵活(视图名、JSON) |
各实现类执行优先级
当容器中存在多个 HandlerExceptionResolver 时,DispatcherServlet 按以下顺序调用(优先级从高到低):
ExceptionHandlerExceptionResolver(mvc:annotation-driven 自动注册,优先级最高);
ResponseStatusExceptionResolver(默认注册,处理 @ResponseStatus 异常);
DefaultHandlerExceptionResolver(默认注册,处理框架标准异常);
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
| @Override public ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { if (shouldApplyTo(request, handler)) { prepareResponse(ex, response); ModelAndView result = doResolveException(request, response, handler, ex); if (result != null) { logException(ex, request); } return result; } else { 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
| @Override protected boolean shouldApplyTo(HttpServletRequest request, Object handler) { if (handler == null) { return super.shouldApplyTo(request, handler); } else if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; handler = handlerMethod.getBean(); return super.shouldApplyTo(request, handler); } else { return false; } }
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) { if (handler != null) { if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) { return true; } if (this.mappedHandlerClasses != null) { for (Class<?> handlerClass : this.mappedHandlerClasses) { if (handlerClass.isInstance(handler)) { return true; } } } } return (this.mappedHandlers == null && this.mappedHandlerClasses == null); }
|
配置场景示例:
通过 setMappedHandlers 或 setMappedHandlerClasses,限制处理器仅处理特定 Controller 的异常:
1 2 3 4 5 6 7 8 9 10
| @Configuration public class ExceptionConfig { @Bean public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() { ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); 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
| protected final ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex); }
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) { ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); if (exceptionHandlerMethod == null) { return null; }
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
ServletWebRequest webRequest = new ServletWebRequest(request, response); ModelAndViewContainer mavContainer = new ModelAndViewContainer();
try { Throwable cause = exception.getCause(); if (cause != null) { exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod); } else { exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod); } } catch (Throwable invocationEx) { return null; }
if (mavContainer.isRequestHandled()) { return new ModelAndView(); } else { ModelMap model = mavContainer.getModel(); HttpStatus status = mavContainer.getStatus(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status); mav.setViewName(mavContainer.getViewName()); if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; } }
|
关键步骤拆解:
(1)getExceptionHandlerMethod:查找 @ExceptionHandler 方法
这是最核心的步骤,查找逻辑优先级为:
- 局部优先:先查找当前 Controller 中标注
@ExceptionHandler 且异常类型匹配的方法;
- 全局兜底:若局部无匹配方法,查找
@ControllerAdvice 标注的类中符合条件的 @ExceptionHandler 方法;
- 异常类型匹配:方法参数中的异常类型需与抛出的异常类型一致或为其父类(如
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
| @Controller public class UserController { @ExceptionHandler(BusinessException.class) public ModelAndView handleBusinessException(BusinessException e) { ModelAndView mav = new ModelAndView("error/business"); mav.addObject("msg", e.getMessage()); return mav; } }
@ControllerAdvice public class GlobalExceptionHandler { @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 方法,支持灵活的参数注入(如 Exception、HttpServletRequest、Model)和返回值类型(如 ModelAndView、String 视图名、@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
| @Override protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { try { if (ex instanceof HttpRequestMethodNotSupportedException) { response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage()); return new ModelAndView(); } else if (ex instanceof HttpMediaTypeNotSupportedException) { 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(code = HttpStatus.BAD_REQUEST, reason = "非法参数") public class InvalidParamException extends RuntimeException { public InvalidParamException(String message) { super(message); } }
@Override protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class); if (responseStatus != null) { try { 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
| <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="defaultErrorView" value="error/default"/> <property name="exceptionMappings"> <props> <prop key="com.example.BusinessException">error/business</prop> <prop key="java.lang.NullPointerException">error/null</prop> </props> </property> <property name="exposeExceptionAttributes" value="true"/> </bean>
|
实战:ExceptionHandlerExceptionResolver 最佳实践
1. 局部 vs 全局异常处理优先级
- 局部处理:Controller 内的
@ExceptionHandler 方法优先于全局 @ControllerAdvice 方法;
- 异常类型精确匹配优先:若局部方法匹配 “具体异常”(如
BusinessException),全局方法匹配 “父异常”(如 Exception),则局部方法生效。
2. 返回 JSON 格式错误响应(前后端分离)
结合 @ResponseBody 或 ResponseEntity,返回标准化 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 { @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 { 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 范围内);
- 解决方案:
- 确保异常类型与方法参数一致;
- 检查方法参数是否支持(可注入
Exception、HttpServletRequest、Model 等);
- 确认
@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 冗余,提升系统可维护性