目录

  1. 背景
  2. Java 异常分类
  3. 示例场景
  4. 是否需要显式捕获异常?
  5. 是否需要显式往外抛出异常?
  6. 哪些异常需要捕获?
  7. 最佳实践
  8. 总结

1. 背景

在 Spring Boot 开发中,异常处理是确保应用程序健壮性和用户体验的关键部分。本文档通过示例代码,讲解异常捕获和抛出的原则。


2. Java 异常分类

异常类型 描述 是否必须捕获 例子
Checked Exception 继承自 Exception(但不是 RuntimeException),编译时强制处理 是(try-catch 或 throws) IOException, SQLException
Unchecked Exception 继承自 RuntimeException,运行时异常,不强制处理 IllegalArgumentException, NullPointerException

3. 示例场景

3.1 服务层代码

1
2
3
4
5
6
7
8
9
@Service
public class OrderService {
public String getOrderDetails(String orderId) {
if (orderId == null) {
throw new IllegalArgumentException("订单 ID 不能为空");
}
return "Order: " + orderId;
}
}

3.2 控制器代码

1
2
3
4
5
6
7
8
9
10
11
@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:

    1
    {"status": 500, "error": "Internal Server Error", "message": "订单 ID 不能为空"}
  • 全局处理:已有 @ControllerAdvice。

4.2 需要显式捕获的情况

  • 自定义响应

    1
    2
    3
    4
    5
    6
    7
    8
    @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:运行时异常自动传播。

    • 示例:

      1
      2
      3
      4
      5
      6
      public String getOrderDetails(String orderId) {
      if (orderId == null) {
      throw new IllegalArgumentException("订单 ID 不能为空");
      }
      return "Order: " + orderId;
      }
  • 受检异常(如 IOException)

    • 需要抛出并声明 throws:如果方法无法处理。

    • 示例:

      1
      2
      3
      public String readFile() throws IOException {
      return Files.readString(Paths.get("file.txt"));
      }

5.2 什么时候不需要抛出?

  • 内部消化

    1
    2
    3
    4
    5
    6
    public String getOrderDetails(String orderId) {
    if (orderId == null) {
    return "默认订单";
    }
    return "Order: " + orderId;
    }
  • 捕获后处理

    1
    2
    3
    4
    5
    6
    7
    8
    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. 最佳实践:全局异常处理

1
2
3
4
5
6
7
8
@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,受检异常需声明。
    • 不需要:内部可消化或无需通知调用者时。
  • 推荐:结合全局处理,抛出语义化异常。