在复杂的 Web 应用开发中,异常处理绝非事后补救,而是保障系统健壮性、提升用户体验的核心设计环节。Spring MVC 深谙此道,其精心设计的 HandlerExceptionResolver
接口架构,为开发者提供了一套强大、灵活且层次分明的异常处理机制,将混乱的异常流转化为可控的业务逻辑。
一、 核心挑战:集中化与统一化的异常处理
Web 请求处理链(DispatcherServlet
-> HandlerMapping
-> HandlerAdapter
-> Controller
)中,任何环节都可能抛出异常。传统 Servlet 的 web.xml
错误页面配置或 Controller 内部的 try-catch
存在显著缺陷:
-
分散性 :
try-catch
导致异常处理逻辑碎片化,重复代码多。 -
侵入性:业务代码被异常处理污染,违反单一职责原则。
-
局限性 :
web.xml
仅能根据状态码或异常类型跳转,无法执行复杂逻辑(如记录日志、构建特定错误模型)。 -
视图耦合:难以将异常信息适配到不同视图(HTML、JSON、XML)。
HandlerExceptionResolver
的设计目标直击痛点:提供一个集中、可插拔、非侵入式的机制,将异常转化为统一的错误响应。
二、 HandlerExceptionResolver:抽象的解耦接口
接口定义简洁而富有表现力:
java
public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex);
}
-
resolveException(...)
:核心方法。当请求处理链中抛出Exception
时,DispatcherServlet
会遍历所有注册的HandlerExceptionResolver
,调用此方法。 -
参数:
-
request
/response
:当前 HTTP 请求/响应对象。 -
handler
:执行时抛出异常的处理器对象(通常是 Controller 方法)。 -
ex
:捕获到的异常。
-
-
返回值:
-
ModelAndView
:表示该解析器已成功处理异常,返回包含错误模型数据和视图信息的对象。 -
null
:表示该解析器无法处理此异常,DispatcherServlet
将继续尝试下一个解析器。 -
抛出新异常:极端情况下,解析器本身也可抛出异常(通常不推荐),此时处理链终止,交由 Servlet 容器处理。
-
-
服务域对象 :
HandlerExceptionResolver
为服务域对象,以单例模式加载并缓存,单实例服务于所有调用,通过多态将exception的包装暴露给扩展者。 -
实体域对象 :参数
Handler
为实体域对象,负责封装元数据,每元数据每实例。 -
会话域对象 :
request、response均为会话域对象
,每线程每实例,封装当前请求上下文。
设计精髓 :通过返回 ModelAndView
或 null
,清晰定义了处理成功、传递责任两种状态,天然支持 责任链模式(Chain of Responsibility)。
三、 分层策略:内置解析器的协同作战
Spring MVC 提供了多种开箱即用的实现,它们形成一条处理链,按特定顺序协同工作:
-
ExceptionHandlerExceptionResolver
(最常用、最强大):-
策略 :驱动
@ExceptionHandler
注解。开发者可在@Controller
或@ControllerAdvice
类中定义方法,并用@ExceptionHandler
标注其能处理的异常类型。 -
优势:
-
强类型:精确匹配异常类型及其子类。
-
灵活响应 :方法可返回
ModelAndView
,String
(视图名),ResponseEntity
, 甚至直接写Response
输出。 -
就近原则 + 全局覆盖 :
@Controller
内方法处理本 Controller 异常;@ControllerAdvice
提供全局统一处理。 -
方法参数自由 :可注入
HttpServletRequest
,HttpServletResponse
, 特定异常对象等。
-
-
适用:现代 Spring MVC/REST 应用的主力,处理业务自定义异常和特定框架异常的主力。
-
-
ResponseStatusExceptionResolver
:-
策略 :处理标注了
@ResponseStatus
注解的异常。 -
原理 :当捕获到的异常类或其父类上有
@ResponseStatus
注解时,该解析器会:-
提取注解中的 HTTP 状态码(如
HttpStatus.NOT_FOUND
)。 -
将状态码设置到
HttpServletResponse
。 -
可选 :尝试使用注解中的
reason
信息或配置的MessageSource
生成一个简单的错误视图(通常用于 REST 则直接返回状态码)。
-
-
适用 :为自定义异常定义语义化的 HTTP 状态码(如
UserNotFoundException
对应 404)。
-
-
DefaultHandlerExceptionResolver
(兜底转换器):-
策略 :将 Spring MVC 内部抛出的常见框架异常(如
NoSuchRequestHandlingMethodException
,HttpRequestMethodNotSupportedException
,TypeMismatchException
等)转换为对应的、符合 HTTP 语义的标准状态码(404, 405, 400 等)。 -
作用 :确保框架自身抛出的异常不会以 500 内部服务器错误笼统返回,而是提供更精确的客户端错误信息。不生成详细错误视图,主要设置状态码。
-
适用:处理 Spring MVC 框架级别的标准异常。
-
-
SimpleMappingExceptionResolver
(传统、直观):-
策略:基于异常类名到错误视图名的映射进行配置。
-
配置项:
-
exceptionMappings
(Properties):键为异常全限定名/父类名,值为视图名(如java.lang.Exception=error/genericError
)。 -
defaultErrorView
:未匹配映射时的默认视图。 -
exceptionAttribute
:暴露到错误视图的模型属性名(默认exception
)。
-
-
特点:配置驱动,无需代码,适合简单的错误页面跳转场景。功能相对基础。
-
适用:传统 Web 应用,需要快速配置统一错误页。
-
四、 设计哲学:责任链与优先级
-
责任链模式 :
DispatcherServlet
按注册顺序 (可通过order
属性调整)遍历HandlerExceptionResolver
链。一旦某个解析器返回非null
的ModelAndView
,处理立即终止,后续解析器不再执行。这种设计:-
职责分离:不同解析器专注处理特定类型或来源的异常。
-
灵活组合:开发者可自由组合、排序或替换内置解析器,也可插入自定义解析器。
-
优雅降级 :精细处理(如
@ExceptionHandler
)优先,通用处理(如DefaultHandlerExceptionResolver
)兜底。
-
-
典型处理流程:
-
请求处理链抛出
Exception ex
。 -
DispatcherServlet
捕获异常,开始遍历HandlerExceptionResolver
链:-
先问
ExceptionHandlerExceptionResolver
:"你能处理ex
吗?"(检查是否有匹配的@ExceptionHandler
方法)。 -
若不能,问
ResponseStatusExceptionResolver
:"ex
或其父类有@ResponseStatus
吗?" -
若不能,问
DefaultHandlerExceptionResolver
:"ex
是 Spring MVC 标准框架异常吗?" -
若不能,问
SimpleMappingExceptionResolver
:"你的配置里有ex
的映射吗?"
-
-
若链中任一解析器 成功处理(返回
ModelAndView
),则使用该结果渲染视图。 -
若所有解析器 都返回
null
,则DispatcherServlet
将异常抛给 Servlet 容器处理(通常导致 500 错误和容器默认错误页)。
-
五、 最佳实践与演进
-
优先使用
@ExceptionHandler
+@ControllerAdvice
:这是最现代、灵活、强大的方式,能精细控制错误响应内容和格式(尤其对 RESTful API 返回 JSON 错误体)。 -
善用
@ResponseStatus
:为自定义业务异常定义清晰的 HTTP 状态码。 -
理解默认转换 :
DefaultHandlerExceptionResolver
保证了框架异常的语义化,无需额外配置。 -
自定义解析器 :实现
HandlerExceptionResolver
接口可处理特殊场景(如特定第三方库异常的统一包装),注册 Bean 并设置合适order
。 -
与
@RestControllerAdvice
结合 :在 REST API 中,使用@RestControllerAdvice
替代@ControllerAdvice
,其内@ExceptionHandler
方法可直接返回ResponseEntity
或错误 DTO 对象,实现纯 JSON 错误响应。 -
安全与信息暴露 :生产环境需注意错误信息中避免暴露敏感数据(堆栈、数据库细节)。可通过自定义解析器或
@ExceptionHandler
进行信息过滤和脱敏。