異常處理機制架構分析
Openstack4j 提供了一套可插拔的 HttpExecutor 框架,支持 Apache HttpClient、Resteasy等,根據分析默認使用 Apache Httpclient。
目前尚不清楚為什麼要提供這個HTTP框架統一層,看起來有些多餘。
但是在吸收這個庫的時候發現,它有着目前為止我個人見過的最優秀的HTTP狀態碼錯誤處理機制。
這個機制可以精確到每個API的級別,在默認的異常狀態碼處理機制之上,可以自由指定是否向上傳播,正如 Java 的 Exception 機制一樣。
相比 OpenFeign,一般處理 Http 錯誤碼時,有一個默認的 feign ErrorDecoder,本身非常方便,但是當需要精確控制異常的時候,還需要自定義 ErrorDecoder。
以下是對這個異常處理機制的梳理,如果用到了就會十分強大。
是怎麼發現的?
在某些API的請求處理過程中,思考過一個問題:有時候API應該向上層拋出,有些API 應該忽略錯誤。
比如説DELETE的時候報錯 404,顯然這個異常可以忽略。
在魔改的過程中,發現了這一優秀設計,並發現部分API使用了這個異常傳播機制。
1. 核心組件關係
Invocation.execute(ExecutionOptions<R>)
↓
HttpResponse.getEntity(returnType, options)
↓
ExecutionOptions.propagate(HttpResponse response)
↓
PropagateOnStatus.propagate(HttpResponse response)
↓
ResponseException.mapException(response, ar.getFault())
↓
拋出具體異常類型
2. BaseOpenStackService.Invocation.execute() 流程
在 BaseOpenStackService.java:232-241 中:
public R execute(ExecutionOptions<R> options) {
header(HEADER_USER_AGENT, USER_AGENT);
HttpRequest<R> request = req.build();
HttpResponse res = HttpExecutor.create().execute(request);
reqIdContainer.set(getRequestId(res));
requestThreadLocal.set(request);
return res.getEntity(request.getReturnType(), options); // 關鍵:傳入options
}
3. PropagateOnStatus 的工作機制
設計原理
PropagateOnStatus 實現了 狀態碼驅動的異常傳播 機制:
- 作用:當 HTTP 響應狀態碼匹配指定值時,自動拋出異常
- 優勢:避免在每個調用處手動檢查狀態碼和處理異常
核心實現
public class PropagateOnStatus implements PropagateResponse {
private final int statusCode;
public static PropagateOnStatus on(int statusCode) {
return new PropagateOnStatus(statusCode);
}
@Override
public void propagate(HttpResponse response) {
if (response.getStatus() == statusCode) { // 狀態碼匹配
ActionResponse ar = ResponseToActionResponse.INSTANCE.apply(response);
throw ResponseException.mapException(response, ar.getFault()); // 拋出異常
}
}
}
4. 使用模式和場景
常見狀態碼使用
- 404 (Not Found): 資源不存在時拋出異常
- 500 (Internal Server Error): 服務器內部錯誤時拋出異常
實際應用示例
// 示例1: FloatingIP 創建 - 網絡404時拋出異常
return post(NeutronFloatingIP.class, uri("/floatingips")).entity(floatingIp)
.execute(ExecutionOptions.create(PropagateOnStatus.on(404)));
// 示例2: Sahara Image 操作 - 鏡像不存在時拋出異常
return post(SaharaImage.class, uri("/images/%s", imageId))
.execute(ExecutionOptions.<SaharaImage>create(PropagateOnStatus.on(404)));
// 示例3: Tacker VNF 創建 - 服務器500錯誤時拋出異常
return post(TackerVnf.class, uri("/vnfs")).entity(vnf)
.execute(ExecutionOptions.<TackerVnf>create(PropagateOnStatus.on(500)));
5. 異常映射機制
ResponseException.mapException() 根據狀態碼自動映射為具體異常類型:
public static ResponseException mapException(String message, int status, Throwable cause) {
if (status == 401)
return new AuthenticationException(message, status, cause); // 認證異常
if (status >= 400 && status < 499)
return new ClientResponseException(message, status, cause); // 客户端異常
if (status >= 500 && status < 600)
return new ServerResponseException(message, status, cause); // 服務端異常
return new ResponseException(message, status, cause); // 通用異常
}
6. 日誌追蹤和調試支持
異常對象會自動包含:
- X-Openstack-Request-Id: OpenStack 請求追蹤ID
- 請求信息: HTTP 方法和URL
- 狀態碼: HTTP 響應狀態碼
public static ResponseException mapException(HttpResponse response, String message) {
ResponseException re = mapException(message, response.getStatus(), null);
re.setRequestInfo(BaseOpenStackService.getRequest()); // 設置請求信息
re.setRequestId(response.header("X-Openstack-Request-Id")); // 設置追蹤ID
return re;
}
7. 優勢和最佳實踐
優勢
1. 代碼簡潔: 避免重複的 if-else 狀態碼檢查
2. 統一異常處理: 集中的異常映射和創建邏輯
3. 調試友好: 自動包含請求追蹤信息
4. 類型安全: 泛型支持不同返回類型
使用建議
1. 明確業務含義: 為需要特殊處理的錯誤碼使用 PropagateOnStatus
2. 合理異常處理: 在調用處捕獲預期的異常類型
3. 日誌記錄: 利用內置的追蹤ID進行問題排查
這種設計模式實現了聲明式異常處理,讓開發者能夠專注於業務邏輯,而將錯誤處理邏輯抽象為配置化的傳播策略。