1. 概述
本文将系统介绍 如何在 Spring REST API 中实现异常处理机制。我们将探讨多种实现方案,它们共同遵循 关注点分离 的核心原则:应用层可以正常抛出异常来表示业务失败,而异常处理则由专门机制统一管理。
2. @ExceptionHandler
通过 @ExceptionHandler
注解,Spring 会在指定异常发生时自动调用标记方法。我们可以通过注解参数或方法参数声明异常类型,后者允许从异常对象提取详细信息进行处理。该方法的特性与控制器方法类似:
- 可返回渲染到响应体的对象或完整的
ResponseEntity
,支持内容协商(Spring 6.2+) - 可返回
ProblemDetail
对象,Spring 会自动设置Content-Type: application/problem+json
- 可通过
@ResponseStatus
指定状态码
最简单的 400 状态码异常处理器:
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(CustomException1.class)
public void handleException1() { }
也可将异常声明为方法参数,例如构建符合 RFC-9457 的错误详情对象:
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler
public ProblemDetail handleException2(CustomException2 ex) {
// ...
}
Spring 6.2 支持为不同内容类型编写独立处理器:
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler( produces = MediaType.APPLICATION_JSON_VALUE )
public CustomExceptionObject handleException3Json(CustomException3 ex) {
// ...
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler( produces = MediaType.TEXT_PLAIN_VALUE )
public String handleException3Text(CustomException3 ex) {
// ...
}
还能处理多种异常类型,使用共享父类作为方法参数:
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({
CustomException4.class,
CustomException5.class
})
public ResponseEntity<CustomExceptionObject> handleException45(Exception ex) {
// ...
}
2.1. 局部异常处理(控制器级别)
将处理器直接放在控制器类中:
@RestController
public class FooController {
//...
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(CustomException1.class)
public void handleException() {
// ...
}
}
✅ 适用于需要控制器专属异常处理的场景
❌ 缺点:无法跨控制器复用,除非通过基类继承(违反组合优于继承原则)
2.2. 全局异常处理
@ControllerAdvice
封装多控制器共享逻辑,是特殊的 Spring 组件。REST API 中推荐使用 @RestControllerAdvice
:
@RestControllerAdvice
public class MyGlobalExceptionHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(CustomException1.class)
public void handleException() {
// ...
}
}
⚠️ 可继承 ResponseEntityExceptionHandler
基类获取预置功能(如 ProblemDetails
生成):
@ControllerAdvice
public class MyCustomResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
@ExceptionHandler({
IllegalArgumentException.class,
IllegalStateException.class
})
ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return super.handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.CONFLICT, request);
}
@Override
protected ResponseEntity<Object> handleHttpMediaTypeNotAcceptable(
HttpMediaTypeNotAcceptableException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
// ... (自定义逻辑,可调用父类方法)
}
}
注:因所有方法返回
ResponseEntity
,此处使用@ControllerAdvice
而非@RestControllerAdvice
3. 直接在异常上使用注解
直接为自定义异常添加 @ResponseStatus
注解:
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class MyResourceNotFoundException extends RuntimeException {
// ...
}
⚠️ 与 DefaultHandlerExceptionResolver
类似,无法控制响应体(仅设置状态码,响应体为 null)
❌ 局限:只能用于自定义异常(无法注解已编译类)
✅ 最佳实践:仅用于边界层特定异常
异常通常继承
RuntimeException
,避免不必要的throws
声明
4. ResponseStatusException
控制器可直接抛出 ResponseStatusException
,支持传入状态码、原因和原始异常:
@GetMapping(value = "/{id}")
public Foo findById(@PathVariable("id") Long id) {
try {
// ...
}
catch (MyResourceNotFoundException ex) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Foo Not Found", ex);
}
}
✅ 优势:
- 快速原型开发
- 松耦合:单异常类型可映射多种状态码(对比
@ExceptionHandler
) - 减少自定义异常类数量
- 程序化控制异常处理
❌ 权衡:
- 缺乏统一处理机制(对比
@ControllerAdvice
的全局性) - 代码重复风险
- 分层架构中仅限控制器层抛出,需包装底层异常
5. HandlerExceptionResolver
自定义 HandlerExceptionResolver
可实现 统一异常处理机制,处理应用抛出的所有异常。
5.1. 现有实现
DispatcherServlet
默认启用三种实现:
-
ExceptionHandlerExceptionResolver
:@ExceptionHandler
的核心组件 -
ResponseStatusExceptionResolver
:@ResponseStatus
的核心组件 DefaultHandlerExceptionResolver
:将标准 Spring 异常映射为 HTTP 状态码(4xx/5xx),完整映射表
⚠️ 仅设置状态码,响应体为 null
5.2. 自定义 HandlerExceptionResolver
为解决响应体控制问题(如根据 Accept
头返回 JSON/XML),需自定义解析器:
@Component
public class RestResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver {
@Override
protected ModelAndView doResolveException(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
try {
if (ex instanceof IllegalArgumentException) {
return handleIllegalArgument(
(IllegalArgumentException) ex, response, handler);
}
// ...
} catch (Exception handlerException) {
logger.warn("Handling of [" + ex.getClass().getName() + "]
resulted in Exception", handlerException);
}
return null;
}
private ModelAndView handleIllegalArgument(
IllegalArgumentException ex, HttpServletResponse response) throws IOException {
response.sendError(HttpServletResponse.SC_CONFLICT);
String accept = request.getHeader(HttpHeaders.ACCEPT);
// ...
return new ModelAndView();
}
}
✅ 关键优势:
- 访问
request
对象,可解析Accept
头 - 通过
ModelAndView
控制响应体
❌ 局限:
- 依赖底层
HttpServletResponse
- 耦合旧 MVC 模型(
ModelAndView
)
6. 补充说明
6.1. 处理现有异常
常见 REST 异常处理场景:
-
AccessDeniedException
:认证用户访问无权限资源(如@PreAuthorize
触发) -
ValidationException
/ConstraintViolationException
:Bean Validation 触发 -
PersistenceException
/DataAccessException
:Spring Data JPA 触发
使用全局异常处理器处理 AccessDeniedException
:
@RestControllerAdvice
public class MyGlobalExceptionHandler {
@ResponseStatus(value = HttpStatus.FORBIDDEN)
@ExceptionHandler( AccessDeniedException.class )
public void handleAccessDeniedException() {
// ...
}
}
6.2. Spring Boot 支持
Spring Boot 提供 ErrorController
实现智能处理错误:
- 浏览器请求:返回 Whitelabel 错误页
- REST 请求:返回 JSON 响应
{
"timestamp": "2019-01-17T16:12:45.977+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Error processing the request!",
"path": "/my-endpoint-with-exceptions"
}
核心配置属性:
-
server.error.whitelabel.enabled
:禁用 Whitelabel 页面 -
server.error.include-stacktrace
:控制堆栈跟踪显示 -
server.error.include-message
:控制消息字段显示(2.3+ 默认隐藏)
自定义响应属性(扩展 DefaultErrorAttributes
):
@Component
public class MyCustomErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(
WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
errorAttributes.put("locale", webRequest.getLocale().toString());
errorAttributes.remove("error");
//...
return errorAttributes;
}
}
自定义特定内容类型错误处理(扩展 BasicErrorController
):
@Component
public class MyErrorController extends BasicErrorController {
public MyErrorController(
ErrorAttributes errorAttributes, ServerProperties serverProperties) {
super(errorAttributes, serverProperties.getError());
}
@RequestMapping(produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<Map<String, Object>> xmlError(HttpServletRequest request) {
// ...
}
}
7. 总结
本文系统对比了 Spring REST API 异常处理的多种方案及其适用场景。实际开发中可灵活组合使用,例如:
- 全局使用
@ControllerAdvice
- 局部使用
ResponseStatusException
选择方案时需权衡:
- 统一性 vs 灵活性
- 代码复用 vs 快速实现
- 分层架构约束
根据项目规模和团队规范选择最适合的异常处理策略。