Skip to content

异常处理 (@ExceptionHandler)

@ExceptionHandler 用于处理控制器方法执行过程中抛出的异常。它可以定义在特定的控制器内,也可以定义在全局的 @ControllerAdvice 类中。

使用示例

java
@Controller
public class SimpleController {

	@ExceptionHandler(IOException.class)
	public ResponseEntity<String> handle(IOException ex) {
		// 返回 internal server error (500) 以及友好提示
		return ResponseEntity.internalServerError().body("读取文件失败:" + ex.getMessage());
	}
}
kotlin
@Controller
class SimpleController {

	@ExceptionHandler(IOException::class)
	fun handle(ex: IOException): ResponseEntity<String> {
		return ResponseEntity.internalServerError().body("读取文件失败:${ex.message}")
	}
}

核心特性

  • 匹配机制:会优先匹配抛出的根异常。如果没找到,会去匹配异常的“起因(Cause)”。Spring 使用 ExceptionDepthComparator 来寻找最匹配的处理方法。
  • 多异常处理:一个方法可以处理多种类型的异常。
  • 内容协商:可以配合 produces 属性针对不同的客户端(如浏览器 vs 移动端)返回不同的错误响应(HTML vs JSON)。

支持的参数与返回值

  • 参数:支持 Exception 对象、HttpServletRequestHttpSessionLocale 等。
  • 返回值:支持 ResponseEntityString (视图名)、ModelAndView 等。如果是 REST 接口,推荐返回 ProblemDetail (RFC 9457标准)。

补充教学

1. 为什么不推荐在 Controller 里写 try-catch?

在 Controller 的业务逻辑里塞满 try-catch 会让代码变得极其难看且高耦合。使用 @ExceptionHandler 可以将错误处理逻辑横向抽取出来,让主逻辑只关注“快乐路径”。

2. 精确匹配原则

如果你同时定义了处理 Exception.classNullPointerException.class 的方法: 当代码抛出 NPE 时,Spring 绝对会走精确匹配的那一个。这允许你定义一个通用的异常处理器兜底,同时为某些常见错误提供精细化的反馈。

3. 内容协商的妙用

在同一个 @ControllerAdvice 里,你可以写两个针对 IllegalArgumentException 的处理方法:

  • 一个 produces = "application/json":给移动端和 AJAX 请求回传 JSON。
  • 一个 produces = "text/html":给直接访问页面的用户返回一个漂亮的错误 HTML 页面。 Spring 会根据请求头的 Accept 自动选择。

Based on Spring Framework.