首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 異常處理完整指南:從原理到架構級最佳實踐

     編輯:Java 相關知識

前言:異常處理是軟件健壯性的基石

Java 異常機制將錯誤傳遞從「返回碼檢查」升級為「類型化、可傳播、可攔截」的結構化流程。本文系統覆蓋異常體系、處理語法、分類哲學、設計原則、反模式、架構級實踐,助你建立生產級異常治理體系。

一、異常體系全景圖


java.lang.Throwable
├── java.lang.Error              // JVM/系統級嚴重錯誤,通常不可恢復
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   ├── NoClassDefFoundError
│   └── ...
└── java.lang.Exception          // 應用層可預期、可恢復問題
    ├── java.lang.RuntimeException // 非受檢異常:程式邏輯錯誤,編譯器不強制處理
    │   ├── NullPointerException
    │   ├── IllegalArgumentException
    │   ├── IndexOutOfBoundsException
    │   └── ...
    └── 受檢異常                // 編譯器強制 catch/throws
        ├── IOException
        ├── SQLException
        ├── ClassNotFoundException
        ├── InterruptedException
        └── ...

核心區別

維度受檢異常非受檢異常Error
編譯器檢查強制 catch 或 throws不強制不強制
典型場景外部資源失效 (IO、DB、網絡)程式 Bug (空指針、越界、參數錯)JVM/系統崩潰
處理策略重試、降級、轉換業務異常修代碼、防御性編程、斷言監控告警、重啟、容災
API 設計方法簽名聲明 `throws`文檔 `@throws`、不聲明不捕獲、不聲明

二、處理語法:try-catch-finally 與 try-with-resources

2.1 基礎結構


try {
    // 高風險代碼
    String line = reader.readLine();
} catch (IOException e) {
    // 處理具體異常
    log.error("讀取失敗", e);
    throw new BusinessException("READ_FAIL", e);
} catch (Exception e) {
    // 兜底 (不推薦常用)
    log.error("未知錯誤", e);
} finally {
    // 無條件執行:資源釋放、狀態重置
    // 即使 catch 中 return/throw 也會執行
    // 除非 System.exit(0)、JVM 崩潰、無限循環
}

2.2 多 catch 規則 (Java 7+)


// 合併捕獲:同層級異常
catch (IOException | SQLException e) {
    log.error("資源訪問異常", e);
}

// 精確捕獲:子類在前,父類在後
catch (FileNotFoundException e) { ... }
catch (IOException e) { ... } // 父類放後

2.3 try-with-resources (Java 7+) —— 資源管理標準化


// 實作 AutoCloseable 的資源自動關閉,即使異常也關閉
try (FileInputStream fis = new FileInputStream("a.txt");
     BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
    return br.readLine();
} // 編譯器自動生成 finally { br.close(); fis.close(); }

// 自定義資源
public class DbConnection implements AutoCloseable {
    public void close() { /* 釋放連接 */ }
}
try (DbConnection conn = pool.getConnection()) {
    // 業務邏輯
}

2.4 異常鏈與轉換


// 保留原始堆疊:cause 參數
throw new BusinessException("DB_ERROR", sqlException);

// 自定義異常類
public class BusinessException extends RuntimeException {
    private final String code;
    public BusinessException(String code, Throwable cause) {
        super(cause.getMessage(), cause); // 必須傳 cause
        this.code = code;
    }
}

三、異常處理設計原則

原則 1:具體問題具體處理

❌ `catch (Exception e) { log }` — 覆蓋式處理,掩蓋真實問題。
✅ 針對 `IOException` 重試、`SQLException` 熔斷、`BusinessException` 返回錯誤碼。

原則 2:異常轉換保留上下文

跨層傳播時,將技術異常包裝為業務異常:


// DAO 層
try { jdbc.update(sql); } 
catch (SQLException e) { throw new DataAccessException("UPDATE_FAIL", e); }

// Service 層
try { dao.update(); }
catch (DataAccessException e) { throw new BusinessException("ORDER_UPDATE_FAIL", e); }

// Controller 層全局處理器統一返回 JSON

原則 3:記錄足夠上下文

  • 完整堆疊追蹤 (`logger.error("msg", e)`)。
  • 關鍵業務參數 (訂單號、用戶 ID、請求 ID)。
  • 環境資訊 (版本、節點、線程池狀態)。

原則 4:不處理可避免的異常


// ❌ 用異常控制流程
try { map.get(key).doSomething(); } catch (NPE) { ... }

// ✅ 防御性檢查
if (map.containsKey(key)) map.get(key).doSomething();
// 或
map.getOrDefault(key, defaultObj).doSomething();

原則 5:失敗快速、失敗明確

  • 參數驗證放在方法開頭 (`Objects.requireNonNull`, `@NotNull`)。
  • 非法狀態立即拋出 `IllegalStateException`,而非返回錯誤碼讓調用者猜。

四、架構級異常治理體系

4.1 分層異常模型

層異常類型職責示例
Controller / Gateway統一響應包裝HTTP 狀態碼、錯誤碼、用戶友好訊息`GlobalExceptionHandler` → `R.fail(400, "參數錯誤")`
Service (業務邏輯)業務異常表達業務規則違反,攜帶錯誤碼/參數`InsufficientBalanceException(code="BAL_001", balance=100)`
Repository / DAO數據訪問異常包裝技術異常,屏蔽實現細節`DataAccessException(cause=SQLException)`
Client / SDK客戶端異常網絡、序列化、超時、熔斷`RemoteServiceException`, `CircuitBreakerOpenException`

4.2 重試與熔斷策略


// 使用 Resilience4j / Spring Retry
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2))
@CircuitBreaker(name = "paymentService", fallbackMethod = "payFallback")
public PaymentResult pay(PaymentRequest req) { ... }

public PaymentResult payFallback(PaymentRequest req, Exception e) {
    return PaymentResult.pending("支付系統繁忙,請稍後查詢結果");
}

4.3 異常監控與告警

  • 日誌聚合 (ELK/Loki) + 關鍵字告警 (`ERROR`, `Exception`)。
  • 業務異常碼統計 (Prometheus Counter `business_exception_total{code="BAL_001"}`)。
  • 追蹤系統關聯 (SkyWalking/Zipkin) 異常調用鏈。

五、反模式與避坑指南

反模式危害修正
吞沒異常 `catch {}`Bug 隱形、難排查至少記錄日誌、或拋出包裝異常
覆蓋式捕獲 `catch (Exception)`掩蓋具體異常、誤處理精確捕獲、多 catch、兜底僅記錄
異常轉換丟失堆疊 `throw new Ex(e.getMessage())`斷裂調用鏈、定位困難`throw new Ex("msg", e)` 保留 cause
用異常做流程控制效能差、語義混淆條件判斷、Optional、Result 模式
在 finally 中拋出異常掩蓋 try/catch 中的原始異常finally 僅釋放資源,內部 try-catch
棄用 checked 異常全包為 RuntimeException調用者感知不到必須處理的風險保留 checked 異常或文檔化 @throws

六、實戰案例:銀行轉賬異常處理


// 業務異常定義
public class InsufficientFundsException extends BusinessException {
    public InsufficientFundsException(String accountId, BigDecimal balance, BigDecimal amount) {
        super("INSUFFICIENT_FUNDS", 
              String.format("賬戶 %s 餘額 %s 不足支付 %s", accountId, balance, amount),
              null);
    }
}

// Service 層
@Transactional
public void transfer(String from, String to, BigDecimal amount) {
    Account fromAcc = accountRepo.findById(from).orElseThrow(
        () -> new BusinessException("ACC_NOT_FOUND", "轉出賬戶不存在"));
    Account toAcc = accountRepo.findById(to).orElseThrow(
        () -> new BusinessException("ACC_NOT_FOUND", "轉入賬戶不存在"));

    if (fromAcc.getBalance().compareTo(amount) < 0)
        throw new InsufficientFundsException(from, fromAcc.getBalance(), amount);

    fromAcc.debit(amount);
    toAcc.credit(amount);
    accountRepo.save(fromAcc);
    accountRepo.save(toAcc);
}

// Controller 全局處理
@ExceptionHandler(InsufficientFundsException.class)
public R handleInsufficient(InsufficientFundsException e) {
    return R.fail(400, e.getMessage()); // 直接透出業務訊息
}

結語

異常處理不是「寫 try-catch」,而是設計錯誤傳遞契約、建立分層異常模型、落地監控告警閉環。從「不吞沒異常」開始,到「業務異常碼化」、再到「重試熔斷自動化」,每一步都在降低系統不確定性。下一篇將實戰《Spring Boot 3 + Resilience4j 異常治理實戰:從零構建高可用微服務錯誤處理體系》。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved