在 Web 应用中,异常是不可避免的。用户的输入不合法,服务的某部分出错,或者数据库连接失败,这些情况都可能触发异常。那么问题来了:如何优雅地捕获并处理这些异常,让用户体验不至于因为一时的错误而受损?
Spring MVC 提供了灵活而强大的异常处理机制,包括局部异常处理和全局异常处理,帮助我们轻松管理这些 “意外情况”。接下来,我们就来详细聊聊如何用 Spring MVC 的异常处理机制,做一个从容应对错误的开发者!
# 1. 异常处理的两种方式
# 1.1 局部异常处理:Controller 内部的 “急救” 机制
如果某些异常仅与特定的控制器相关,我们可以直接在该 Controller 中捕获并处理这些异常。
@ExceptionHandler 注解用于指定一个方法处理某种特定的异常。这种方式的好处是逻辑清晰,控制器自己的异常自己处理。
示例:局部异常处理
@Controller | |
@RequestMapping("/user") | |
public class UserController { | |
@GetMapping("/{id}") | |
public String getUser(@PathVariable int id) { | |
if (id <= 0) { | |
throw new IllegalArgumentException("Invalid user ID"); | |
} | |
return "userView"; | |
} | |
@ExceptionHandler(IllegalArgumentException.class) | |
public String handleIllegalArgumentException(IllegalArgumentException e, Model model) { | |
model.addAttribute("errorMessage", e.getMessage()); | |
return "error"; | |
} | |
} |
运行效果:
- 当用户访问
/user/-1时,抛出的IllegalArgumentException会被handleIllegalArgumentException方法捕获。- 返回错误页面
error,并在页面上显示错误信息Invalid user ID。
# 1.2 全局异常处理:让异常不再 “局限于一隅”
对于跨多个 Controller 的通用异常,例如权限校验失败、参数格式不合法等,局部处理显然不够灵活。此时,我们可以使用全局异常处理机制。
关键注解: @ControllerAdvice
@ControllerAdvice是 Spring MVC 提供的全局异常处理工具。- 它会扫描所有标注了
@Controller的类,捕获其中抛出的异常。
示例:全局异常处理
@ControllerAdvice | |
public class GlobalExceptionHandler { | |
@ExceptionHandler(IllegalArgumentException.class) | |
public String handleIllegalArgumentException(IllegalArgumentException e, Model model) { | |
model.addAttribute("errorMessage", e.getMessage()); | |
return "error"; | |
} | |
@ExceptionHandler(Exception.class) | |
public String handleGenericException(Exception e, Model model) { | |
model.addAttribute("errorMessage", "An unexpected error occurred: " + e.getMessage()); | |
return "error"; | |
} | |
} |
运行效果:
- 不管哪个 Controller 抛出
IllegalArgumentException,都会由handleIllegalArgumentException方法处理。- 如果捕获不到具体的异常类型,就可以指定为 Exception 异常大类,捕获到之后进入通用异常处理逻辑。
# 2. 实践中的常见用法
# 2.1 返回 JSON 格式的错误信息
在前后端分离的项目中,返回错误页面显然不是最佳选择。通过 @ResponseBody 或 **@RestControllerAdvice**(@ControllerAdvice 和 @ResponseBody 的组合注解 ) ,可以直接返回 JSON 格式的错误信息。可以返回一个对象或者 Map 集合来自动处理成 JSON 格式返回
示例:全局 JSON 异常处理
@RestControllerAdvice | |
public class GlobalRestExceptionHandler { | |
@ExceptionHandler(IllegalArgumentException.class) | |
public Map<String, Object> handleIllegalArgumentException(IllegalArgumentException e) { | |
Map<String, Object> response = new HashMap<>(); | |
response.put("error", true); | |
response.put("message", e.getMessage()); | |
return response; | |
} | |
@ExceptionHandler(Exception.class) | |
public Map<String, Object> handleGenericException(Exception e) { | |
Map<String, Object> response = new HashMap<>(); | |
response.put("error", true); | |
response.put("message", "An unexpected error occurred"); | |
return response; | |
} | |
} |
运行效果:
例如请求 /user/-1 ,则会返回:
{ | |
"error": true, | |
"message": "Invalid user ID" | |
} |
# 2.2 自定义异常与枚举
将异常信息和状态码绑定到自定义异常中,让错误响应更加规范化。
定义枚举:错误类型与状态码
public enum ErrorCode { | |
INVALID_USER_ID(400, "Invalid user ID"), | |
SERVER_ERROR(500, "Internal Server Error"); | |
private final int code; | |
private final String message; | |
ErrorCode(int code, String message) { | |
this.code = code; | |
this.message = message; | |
} | |
public int getCode() { | |
return code; | |
} | |
public String getMessage() { | |
return message; | |
} | |
} |
自定义异常
public class CustomException extends RuntimeException { | |
private final ErrorCode errorCode; | |
public CustomException(ErrorCode errorCode) { | |
super(errorCode.getMessage()); | |
this.errorCode = errorCode; | |
} | |
public int getCode() { | |
return errorCode.getCode(); | |
} | |
} |
统一异常处理
@RestControllerAdvice | |
public class GlobalCustomExceptionHandler { | |
@ExceptionHandler(CustomException.class) | |
public ResponseEntity<Map<String, Object>> handleCustomException(CustomException e) { | |
Map<String, Object> response = new HashMap<>(); | |
response.put("error", true); | |
response.put("code", e.getCode()); | |
response.put("message", e.getMessage()); | |
return new ResponseEntity<>(response, HttpStatus.valueOf(e.getCode())); | |
} | |
} |
【我们来对其中的一些代码片段做一些解释】
返回值
ResponseEntity<Map<String, Object>>说明:
ResponseEntity是 Spring 提供的响应封装类,用于设置 HTTP 状态码和响应体。其中 **HttpStatus.valueOf(e.getCode())是根据异常中的状态码 ** 设置的 HTTP 响应的状态码。Map<String, Object>是响应体的数据结构,存储自定义的错误信息。
* 注意区别 *:HTTP 响应状态码和 JSON 响应体中的状态码是 * 两种不同的内容 *
运行效果:
-
在一些逻辑判断之后,手动抛出自定义异常:
throw new CustomException(ErrorCode.INVALID_USER_ID);
-
响应结果:
{"error": true,
"code": 400,
"message": "Invalid user ID"
}
# 3. 局部与全局处理的对比
| 特性 | 局部异常处理 | 全局异常处理 |
|---|---|---|
| 适用范围 | 单个 Controller 中 | 所有 Controller |
| 注解 | @ExceptionHandler |
@ControllerAdvice |
| 优点 | 定制化处理,逻辑清晰 | 统一处理异常,代码更简洁 |
| 缺点 | 只能处理局部异常,扩展性差 | 可能需要额外判断 Controller 作用域 |
# 4. 总结
- 局部异常处理:使用
@ExceptionHandler捕获特定 Controller 的异常,适合个性化逻辑需求。 - 全局异常处理:通过
@ControllerAdvice实现全局异常管理,特别适用于通用异常类型。 - 高级用法:结合
@RestControllerAdvice返回 JSON 错误信息,自定义异常类与状态码,让接口更规范。
在实际项目中,局部与全局处理可以结合使用。局部处理特殊逻辑,全局处理通用错误,真正做到优雅应对各种 “意外”。
