闽公网安备 35020302035485号
// 错误示例:捕获通用 Exception,丢失具体异常信息
try {
// 可能抛出 IOException 或 SQLException 的代码
} catch (Exception e) {
e.printStackTrace(); // 仅打印堆栈,未处理具体异常
}
// 正确示例:捕获具体异常,分层处理
try {
readFile("data.txt");
saveData(conn, data);
} catch (FileNotFoundException e) {
log.error("文件不存在: {}", filePath, e); // 记录上下文信息
thrownewBusinessException("数据文件缺失,请联系管理员", e); // 转换为业务异常
} catch (SQLException e) {
log.error("数据库保存失败: {}", data, e);
conn.rollback(); // 事务回滚
thrownewRetryableException("数据库临时故障,请重试", e); // 标记为可重试异常
} finally {
// 关闭资源(Java 7 前的传统方式)
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
log.warn("关闭连接失败", e); // 资源关闭异常不影响主流程
}
}
}
核心要点:• finally 用于必须执行的资源清理(如关闭流、释放锁),但需注意:若 finally 中抛出异常,会覆盖 try/catch 中的异常,导致原始异常丢失。
// 错误示例:手动关闭资源,易遗漏或抛出异常
InputStreamin=newFileInputStream("file.txt");
try {
// 使用资源
} catch (IOException e) {
log.error("读取失败", e);
} finally {
if (in != null) {
try {
in.close(); // 可能抛出异常,覆盖原始异常
} catch (IOException e) {
log.warn("关闭失败", e);
}
}
}
// 堆代码 duidaima.com
// 正确示例:try-with-resources 自动关闭资源
try (InputStreamin=newFileInputStream("file.txt");
BufferedReaderreader=newBufferedReader(newInputStreamReader(in))) {
// 使用资源,无需手动关闭
Stringline= reader.readLine();
} catch (IOException e) {
log.error("文件处理失败", e); // 原始异常完整保留
}
优势:资源自动关闭,即使 try 或 catch 中抛出异常,资源也会被正确释放,且不会覆盖原始异常。if (userId == null) {
throw new IllegalArgumentException("用户ID不能为空"); // 明确参数错误
}
• throws:在方法签名中声明可能抛出的异常,告知调用方"此处可能出错,需处理或继续抛出"。public User getUser(Long id) throws SQLException, UserNotFoundException {
// 可能抛出数据库异常或用户不存在异常
}
最佳实践:• throws 声明最小必要的异常类型,避免 throws Exception 迫使调用方捕获无关异常。
try {
// 复杂业务逻辑,可能抛出多种异常
} catch (Exception e) {
log.info("操作失败"); // 仅记录简单信息,丢失异常类型和堆栈
}
问题分析:try {
// 业务逻辑
} catch (NullPointerException e) {
log.error("空指针异常,检查参数: {}", param, e); // 逻辑错误,需修复代码
} catch (IOException e) {
log.error("IO异常,文件路径: {}", path, e); // 外部资源错误,需处理
} catch (BusinessException e) {
log.warn("业务异常: {}", e.getMessage()); // 已知业务规则,无需堆栈
}
错误 2:空 catch 块(异常静默失败)try {
Integer.parseInt(userInput);
} catch (NumberFormatException e) {
// 什么都不做,希望程序继续执行
}
问题分析:try {
Integer.parseInt(userInput);
} catch (NumberFormatException e) {
log.error("用户输入格式错误: '{}'", userInput, e); // 记录原始输入
throw new ValidationException("请输入有效的数字", e); // 告知用户具体错误
}
错误 3:finally 块中使用 returnpublic int calculate() {
try {
return 1 / 0; // 抛出 ArithmeticException
} catch (Exception e) {
return -1;
} finally {
return 0; // finally 中的 return 覆盖异常和 catch 的返回值
}
}
执行结果:返回 0,而非 -1,且异常被静默忽略。• JVM 规范明确:finally 中的 return 会终止异常传播。
public int calculate() {
int result = -1;
try {
result = 1 / 0;
} catch (ArithmeticException e) {
log.error("计算失败", e);
// 保持 result 为 -1
} finally {
// 仅清理资源,无返回
}
return result;
}
错误 4:忽略异常链传递(丢失原始异常)try {
readConfig("app.properties");
} catch (IOException e) {
throw new RuntimeException("配置读取失败"); // 丢失原始异常堆栈
}
问题分析:try {
readConfig("app.properties");
} catch (IOException e) {
// 构造新异常时传入原始异常
throw new ConfigurationException("配置读取失败", e);
}
效果:日志中会显示完整堆栈,包含 ConfigurationException 和 IOException 的因果关系,快速定位问题。// 错误:用异常判断列表是否为空
List<User> users = getUserList();
try {
User first = users.get(0); // 列表为空时抛出 IndexOutOfBoundsException
process(first);
} catch (IndexOutOfBoundsException e) {
log.info("用户列表为空,执行默认逻辑");
}
问题分析:List<User> users = getUserList();
if (users.isEmpty()) {
log.info("用户列表为空,执行默认逻辑");
} else {
process(users.get(0));
}
错误 6:资源未关闭导致泄漏(try-with-resources 缺失)// Java 7 前未使用 try-with-resources,资源关闭遗漏
InputStreamin=null;
try {
in = newFileInputStream("largeFile.txt");
// 处理文件(可能抛出异常,导致 in.close() 未执行)
} catch (IOException e) {
log.error("处理失败", e);
} finally {
// 若 in 为 null(如构造器失败),则跳过关闭
if (in != null) {
in.close(); // 此处可能抛出 IOException,覆盖原始异常
}
}
问题分析:try (InputStream in = new FileInputStream("largeFile.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
// 处理文件,无需手动关闭
} catch (IOException e) {
log.error("处理失败", e); // 原始异常完整保留
}
错误 7:自定义异常设计不当// 错误:自定义异常继承 Exception(受检异常),但无需强制处理
publicclassDataNotFoundExceptionextendsException {
publicDataNotFoundException(String message) {
super(message);
}
}
// 调用方被迫处理或声明抛出
public User getUser(Long id)throws DataNotFoundException {
// ...
}
问题分析:public classDataNotFoundExceptionextendsRuntimeException {
privatefinal String errorCode;
privatefinalboolean retryable;
publicDataNotFoundException(String message, String errorCode, boolean retryable) {
super(message);
this.errorCode = errorCode;
this.retryable = retryable;
}
// getter 方法
}
错误 8:日志记录不完整(缺少上下文)try {
updateUser(user);
} catch (SQLException e) {
log.error("更新用户失败"); // 仅记录消息,无用户ID、SQL语句等上下文
}
问题分析:// 错误:将逻辑错误定义为受检异常
publicclassInvalidParamExceptionextendsException {
// ...
}
// 调用方必须捕获或声明,增加冗余代码
publicvoidvalidateParam(String param)throws InvalidParamException {
if (param == null) {
thrownewInvalidParamException("参数为空");
}
}
问题分析:// 底层方法抛出受检异常
publicvoidreadFile()throws IOException { ... }
// 中间层不处理,直接抛出
publicvoidprocessData()throws IOException { readFile(); }
// 最终传递到 Controller,被迫捕获
@GetMapping("/data")
public String getData() {
try {
service.processData();
} catch (IOException e) {
return"操作失败"; // 无法区分是文件不存在还是权限不足
}
}
问题分析:public void processData() {
try {
readFile();
} catch (FileNotFoundException e) {
throw new BusinessException("数据文件缺失,请联系管理员", e); // 用户可理解的消息
} catch (IOException e) {
throw new SystemException("文件系统故障", "FS-001", e); // 系统级错误,标记错误码
}
}
三、异常处理最佳实践(2023-2025 最新指南)| 层级 | 职责 | 处理方式 |
| 底层(工具/DAO) | 抛出原始异常,不处理业务逻辑 | throws SQLException、IOException |
| 中间层(Service) | 转换异常类型,附加业务上下文 | 捕获底层异常 → 抛出 BusinessException |
| 顶层(Controller) | 统一异常响应,用户友好提示 | 捕获业务异常 → 返回标准化错误响应 |
@RestControllerAdvice
publicclassGlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
ErrorResponseerror=newErrorResponse(e.getErrorCode(), e.getMessage());
returnnewResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(RetryableException.class)
public ResponseEntity<ErrorResponse> handleRetryableException(RetryableException e) {
ErrorResponseerror=newErrorResponse(e.getErrorCode(), e.getMessage() + ", 建议10秒后重试");
returnnewResponseEntity<>(error, HttpStatus.SERVICE_UNAVAILABLE);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleUnknownException(Exception e) {
log.error("未处理异常", e); // 记录完整堆栈
ErrorResponseerror=newErrorResponse("SYSTEM_ERROR", "系统繁忙,请稍后再试");
returnnewResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
2. Java 9+ 新特性:提升异常处理效率// Java 7-:必须在 try 中声明资源
try (BufferedReaderreader=newBufferedReader(newFileReader("file.txt"))) { ... }
// Java 9+:可使用外部声明的资源(需为 final 或 effectively final)
BufferedReaderreader=newBufferedReader(newFileReader("file.txt"));
try (reader) { // 直接使用变量
...
}
2.2 Java 21:switch 异常处理(预览特性 JEP 8323658)// 传统方式:需在 switch 外 try-catch
try {
Objectresult=switch (input) {
case Integer i -> processNumber(i);
case String s -> processString(s);
default -> thrownewIllegalArgumentException("不支持的类型");
};
} catch (IllegalArgumentException e) {
log.error("处理失败", e);
}
// Java 21+:在 switch 中处理异常(预览特性)
Objectresult=switch (input) {
case Integer i -> processNumber(i);
case String s -> processString(s);
casenull -> thrownewNullPointerException("输入不能为空");
default -> thrownewIllegalArgumentException("不支持的类型: " + input);
};
优势:将 null 检查和异常处理整合到 switch 中,减少嵌套。import io.vavr.control.Either;
// 函数返回 Either<异常, 结果>,而非抛出异常
public Either<AppException, User> findUser(Long id) {
try {
Useruser= userRepository.findById(id);
return user != null ? Either.right(user) : Either.left(newUserNotFoundException(id));
} catch (SQLException e) {
return Either.left(newDatabaseException("查询失败", e));
}
}
// 调用方处理:链式调用,清晰分离成功/失败逻辑
findUser(123)
.map(user -> user.getName()) // 成功时处理
.leftMap(e -> { // 失败时处理不同异常类型
if (e instanceof UserNotFoundException) {
log.warn("用户不存在: {}", e.getMessage());
return"默认用户";
} else {
log.error("系统异常", e);
return"系统错误";
}
})
.forEach(name -> System.out.println("用户名: " + name));
优势:{
"errorCode":"USER_NOT_FOUND",
"message":"用户ID为123的用户不存在",
"details":{"userId":"123"},
"timestamp":"2025-08-15T10:30:00Z",
"traceId":"abc-123-xyz"// 分布式追踪ID
}
4.2 异常转换:避免暴露内部实现@FeignClient(name = "user-service")
publicinterfaceUserServiceClient {
@GetMapping("/users/{id}")
UserDTO getUser(@PathVariable("id") Long id);
}
// 调用方处理Feign异常
public UserDTO getUser(Long id) {
try {
return userClient.getUser(id);
} catch (FeignException e) {
if (e.status() == 404) {
thrownewUserNotFoundException("用户不存在: " + id);
} elseif (e.status() == 503) {
thrownewServiceUnavailableException("用户服务暂时不可用", e);
} else {
thrownewSystemException("调用用户服务失败", e);
}
}
}
5. 单元测试:验证异常行为// 使用 JUnit 5 测试异常
@Test
voidshouldThrowUserNotFoundWhenIdInvalid() {
// 准备
UserServiceservice=newUserService(mockRepo);
// 执行 & 验证
assertThrows(UserNotFoundException.class, () -> service.getUser(999L),
"当用户ID不存在时,应抛出UserNotFoundException");
// 验证异常详情
UserNotFoundExceptionexception= assertThrows(UserNotFoundException.class,
() -> service.getUser(999L));
assertEquals("USER_NOT_FOUND", exception.getErrorCode());
assertTrue(exception.isRetryable() == false);
}
关键测试点:• 资源是否正确释放(如数据库连接关闭)。
四、构建健壮的异常处理体系