目录
1. 背景
在 Spring Boot 开发中,异常处理是确保应用程序健壮性和用户体验的关键部分。本文档通过示例代码,讲解异常捕获和抛出的原则。
2. Java 异常分类
| 异常类型 | 描述 | 是否必须捕获 | 例子 |
|---|---|---|---|
| Checked Exception | 继承自 Exception(但不是 RuntimeException),编译时强制处理 | 是(try-catch 或 throws) | IOException, SQLException |
| Unchecked Exception | 继承自 RuntimeException,运行时异常,不强制处理 | 否 | IllegalArgumentException, NullPointerException |
3. 示例场景
3.1 服务层代码
@Service
public class OrderService {
public String getOrderDetails(String orderId) {
if (orderId == null) {
throw new IllegalArgumentException("订单 ID 不能为空");
}
return "Order: " + orderId;
}
}
3.2 控制器代码
@RestController
@RequestMapping("/api")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/orders/{id}")
public String getOrder(@PathVariable String id) {
return orderService.getOrderDetails(id); // 未显式捕获
}
}
4. 是否需要显式捕获异常?
4.1 不需要显式捕获的情况
-
默认处理:Spring Boot 返回 500:
{"status": 500, "error": "Internal Server Error", "message": "订单 ID 不能为空"} -
全局处理:已有 @ControllerAdvice。
4.2 需要显式捕获的情况
-
自定义响应:
@GetMapping("/orders/{id}") public ResponseEntity<String> getOrder(@PathVariable String id) { try { return ResponseEntity.ok(orderService.getOrderDetails(id)); } catch (IllegalArgumentException e) { return ResponseEntity.badRequest().body("错误: " + e.getMessage()); } }
5. 是否需要显式往外抛出异常?
5.1 什么时候需要抛出?
-
未检查异常(如 IllegalArgumentException) :
-
需要抛出:表示方法无法继续执行,通知调用者问题。
-
无需声明 throws:运行时异常自动传播。
-
示例:
public String getOrderDetails(String orderId) { if (orderId == null) { throw new IllegalArgumentException("订单 ID 不能为空"); } return "Order: " + orderId; }
-
-
受检异常(如 IOException) :
-
需要抛出并声明 throws:如果方法无法处理。
-
示例:
public String readFile() throws IOException { return Files.readString(Paths.get("file.txt")); }
-
5.2 什么时候不需要抛出?
-
内部消化:
public String getOrderDetails(String orderId) { if (orderId == null) { return "默认订单"; } return "Order: " + orderId; } -
捕获后处理:
public String getOrderDetails(String orderId) { try { return fetchOrder(orderId); } catch (Exception e) { log.error("获取失败", e); return null; } }
5.3 Spring Boot 中的建议
- 运行时异常:抛出但无需声明 throws,交给全局处理器。
- 受检异常:抛出并声明 throws,或在适当层级捕获。
6. 哪些异常需要捕获?
| 异常类型 | 是否需要捕获? | 原因及建议 |
|---|---|---|
| Checked Exception | 是 | 编译器强制要求。 |
| IllegalArgumentException | 否(视情况) | 可抛出,交给全局处理;若需特定响应,则捕获。 |
| Custom Business Exception | 否(推荐全局处理) | 抛出自定义异常,统一处理。 |
7. 最佳实践:全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> handleIllegalArgument(IllegalArgumentException e) {
return Collections.singletonMap("error", e.getMessage());
}
}
8. 总结
-
捕获:受检异常必须捕获,运行时异常可选。
-
抛出:
- 需要:无法处理时抛出,运行时异常无需 throws,受检异常需声明。
- 不需要:内部可消化或无需通知调用者时。
-
推荐:结合全局处理,抛出语义化异常。