异常处理 (@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对象、HttpServletRequest、HttpSession、Locale等。 - 返回值:支持
ResponseEntity、String(视图名)、ModelAndView等。如果是 REST 接口,推荐返回ProblemDetail(RFC 9457标准)。
补充教学
1. 为什么不推荐在 Controller 里写 try-catch?
在 Controller 的业务逻辑里塞满 try-catch 会让代码变得极其难看且高耦合。使用 @ExceptionHandler 可以将错误处理逻辑横向抽取出来,让主逻辑只关注“快乐路径”。
2. 精确匹配原则
如果你同时定义了处理 Exception.class 和 NullPointerException.class 的方法: 当代码抛出 NPE 时,Spring 绝对会走精确匹配的那一个。这允许你定义一个通用的异常处理器兜底,同时为某些常见错误提供精细化的反馈。
3. 内容协商的妙用
在同一个 @ControllerAdvice 里,你可以写两个针对 IllegalArgumentException 的处理方法:
- 一个
produces = "application/json":给移动端和 AJAX 请求回传 JSON。 - 一个
produces = "text/html":给直接访问页面的用户返回一个漂亮的错误 HTML 页面。 Spring 会根据请求头的Accept自动选择。