接續上一篇說的
我們使用 instanceof 來讓Trowable 認得不同錯誤處理
不過我們可以發現
這樣jmsTemplate機制我們會一直修改(JmsTemplateConfiguration設定檔中的errorHandler)
factory.setErrorHandler(jmsErrorHandler);
如果不小心動到有可能導致整個系統有錯誤發生
所以如果我們可以設定一個彈性機制來處理
就可以降低去調整JmsTemplateConfiguration設定檔中的errorHandler
概念上是提升易用性和擴充性
如果以機制底層角度去理解是更複雜的
『主要是方便是要擴充應用情境的人會比較方便』
以下使用到的概念為裝封與多型(物件導向特性的其中2個,還有1個為繼承)
目標:降低去調整JmsTemplateConfiguration設定檔中的errorHandler
因此我們要設計一個介面(interface)未來大家只要實作就可以了,
沿用上次的例子做一個說明:
(1) 更細部的分類
ApException: 商業邏輯上的錯誤 -> 訂單錯誤、商品推薦錯誤 這兩個屬於商業邏輯上的錯 把ApException 上升1個層次變成 “Ap介面”
DocException: 文件上下傳的錯誤 -> 上傳錯誤、下載錯誤、格式錯誤 把DocException 上升1個層次變成 "Doc介面“
AccountException: 帳號處理的錯誤 -> 密碼相關錯誤、權限相關錯誤 把AccountException 上升1個層次變成 "Account介面“
JMSException: jmsTemplate預設的錯誤 因這個是原生錯誤 就不做調整
(2) 用系統作為單位來分類
假設這個jms 專案是一個可以納入1個以上系統的平台 那我們可以把1個系統的jms錯誤處理寫成1個介面 -> "procduct介面"
『前提是系統間會溝通會比較適用這個情境,因為如果個別都資訊沒有交換錯誤處理機制其實可以自行管理就不一定使用這樣擴充性的寫法』
沿用上篇文章當作例子改寫程式,為了不再增加Exception因此我以(2)用系統作為單位來分類
@Slf4j
@Configuration
public class JmsErrorHandler implements ErrorHandler {
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
@Override
public void handleError(Throwable t) {
Throwable retrieved = t.getCause();
// 把之前 片段(1)部分刪除
}
}
片段(2)
@Autowired(required = false)
List<ExceptionHandlerInterface> exceptionHandlerSources;
if (exceptionHandlerSources != null && Boolean.FALSE.equals(exceptionHandlerSources.isEmpty())) {
for (ExceptionHandlerInterface handler : exceptionHandlerSources) {
errorResponse = handler.allExceptionHandler(retrieved);
if (errorResponse != null) {
isHit = true;
break;
}
}
}
先介紹這個概念要註冊 錯誤處理時等於會去找是否有人實作了這些介面有的話會載入並註冊
變成程式的話就是
exceptionHandlerSources這個介面List不為null也不為空,就遍歷所有的並呼叫handler做例外處理
那個break部份是因為如果找到可以處理的方式就離開,是希望這個複雜度不要到O(n)到O(1)會比較好一些
那我們現在來看看這介面實際長什麼樣子
ExceptionHandlerInterface.class
public interface ExceptionHandlerInterface {
ContextJson<Void> allExceptionHandler(Throwable throwable);
}
就這麼的單純接下Throwable在看要做什麼
那來實作一下
ProductExceptionHandler.class
@Slf4j
@Configuration
public class ProductExceptionHandler implements ExceptionHandlerInterface {
public ContextJson<Void> allExceptionHandler(Throwable throwable) {
// 這邊有沒有很熟悉 就是上1文章的片段(1)去改寫一下
if (throwable instanceof ApException) {
return handleSysRunnerException((ApException) throwable);
}
if (throwable instanceof DocException) {
return handleResourceNotFound((DocException) throwable);
}
return null;
}
public ContextJson<Void> handleSysRunnerException(final ApException ex) {
ContextJson<Void> res = ContextJson.failure("商業邏輯錯誤",apException);
log.info(gson.toJson(res)); // 因為此範例會將錯誤顯示在server log/ console
return res;
}
public ContextJson<Void> handleSysRunnerException(final DocException ex) {
ContextJson<Void> res = ContextJson.failure("文件輸入錯誤",docException);
log.info(gson.toJson(res)); // 因為此範例會將錯誤顯示在server log/ console
return res;
}
}
最後我們把這機制
拼裝好
JmsErrorHandler.class
@Slf4j
@Configuration
public class JmsErrorHandler implements ErrorHandler {
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
@Autowired(required = false)
List<ExceptionHandlerInterface> exceptionHandlerSources;
@Override
public void handleError(Throwable t) {
Throwable retrieved = t.getCause();
// 片段(2)
if (exceptionHandlerSources != null && Boolean.FALSE.equals(exceptionHandlerSources.isEmpty())) {
for (ExceptionHandlerInterface handler : exceptionHandlerSources) {
errorResponse = handler.allExceptionHandler(retrieved);
if (errorResponse != null) {
isHit = true;
break;
}
}
}
}
}
這樣的JmsErrorHandler 就完成了
把錯誤機制封裝好,未來其他人不需要異動到這邊的程式碼
只需要實作ExceptionHandlerInterface後就會自動載入並註冊
依照現在這樣的機制還有一點不完整
會發現他有處理兩個錯誤,那非這兩個錯誤外的話該怎麼辦?
我們必須再加上一個片段(3)
if (Boolean.FALSE.equals(isHit)) {
errorResponse = handleException((Exception) retrieved);
}
log.info(gson.toJson(errorResponse));
當所有ExceptionHandlerInterface都沒有例外處理,就表示isHit旗標為False因此用最大Exception進行處理
完整JmsErrorHandler.class
@Slf4j
@Configuration
public class JmsErrorHandler implements ErrorHandler {
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
@Autowired(required = false)
List<ExceptionHandlerInterface> exceptionHandlerSources;
@Override
public void handleError(Throwable t) {
Throwable retrieved = t.getCause();
// 片段(2)
if (exceptionHandlerSources != null && Boolean.FALSE.equals(exceptionHandlerSources.isEmpty())) {
for (ExceptionHandlerInterface handler : exceptionHandlerSources) {
errorResponse = handler.allExceptionHandler(retrieved);
if (errorResponse != null) {
isHit = true;
break;
}
}
}
if (Boolean.FALSE.equals(isHit)) {
errorResponse = handleException((Exception) retrieved);
}
log.info(gson.toJson(errorResponse));
}
}
補充:
需要有mq服務 建議可以使用containter方便又快速
主要是給自己的一個紀錄,也分享給有需要的夥伴
關於這個方式,載很多客製化設定時可以這樣設計。
這是一個心血來潮,產生的文章
若有喜歡或交流的部分都歡迎在下方留言,多多關照。