您現在的位置是:首頁 > 籃球

寫了這麼久的業務連異常都不知道怎麼處理嗎?

  • 由 程式設計菌zfn 發表于 籃球
  • 2021-11-28
簡介}public Result(Boolean success, Integer errorCode, String errorMsg, T data) {this

資料自動開啟怎麼辦

一個案例

是這樣的,這個案例是自身經歷的一個例子哈,我想大家都用過微服務吧!

我先說說背景哈,我有這樣的一個場景,就是我支付完成之後,我是不是要去給使用者發貨,但是給使用者發揮之前,我需要呼叫一下風控的服務,如果這個使用者風控判斷有風險的話,我就會攔截這個發貨行為,因為這個系統是做海外系統的,那我再呼叫風控系統之前要做這樣一個事情,就是把海外的本地幣種轉化為USD的統一的匯率,這就設計到了匯率的服務了,然後呢有一天假設我做了一些更改,然後需要把我支付這個服務,和我調用匯率介面的服務一起升級,但是因為我不小心,導致忘記發匯率介面,然後就會出現一個問題,因為我匯率服務本身,裡面是處理了異常的,就算我沒有升級這個服務,你一樣也可以調通這個服務,但是我會把我自己的異常轉換成code,然後支付調用匯率這個服務,根據code的值成功或者失敗去做邏輯,但是呢因為我支付服務,如果呼叫成功的話,我就把轉換後的金額傳給風控,如果轉換返回失敗,我就給一個預設值0,但是這樣一寫邏輯就碰到一個問題,風控那邊會根據金額來做策略,它有一個策略就是攔截金額為0的訂單,不發貨,所以那一次升級,就是因為這個邏輯導致了有6k多單的發貨沒有被髮貨,導致了這個事故,像上面這個問題,其實我們覺得我們不應該說幫人家去做決策,如果失敗的話,我們不應該說給一個預設值,而是拋異常出去才對,這才是正確的做法,就是很多時候,我們自己並不知道是給一個業務code的錯誤,還是拋一個Exception,像很多其他的不那麼嚴謹的業務,可能並不說考慮的那麼清楚,但是我們支付就必須一點點都得考慮的很嚴謹了,像這個事故,我們還有很多不足的地方,延遲發貨的告警沒有告,調用匯率介面失敗了,也沒有告警,而是把原生的錯誤轉換了等等。所以小六六這邊才覺得,很多的時候,我們自己確實是不知道如何的處理一些業務的異常,應該怎麼樣給其他服務返回,才能讓呼叫你的服務的人,覺得你這個服務的設計上好的,等等,這就是我想跟大家聊的這篇文章。

不過我們還是先來了解下Java的異常體系吧!

什麼是異常

異常是程式中的一些錯誤,但並不是所有的錯誤都是異常,並且錯誤有時候是可以避免的。

比如說,你的程式碼少了一個分號,那麼執行出來結果是提示是錯誤java。lang。Error;如果你用System。out。println(11/0),那麼你是因為你用0做了除數,會丟擲java。lang。ArithmeticException的異常。

異常發生的原因有很多,通常包含以下幾大類:

使用者輸入了非法資料。

要開啟的檔案不存在。

網路通訊時連線中斷,或者JVM記憶體溢位。

要理解Java異常處理是如何工作的,你需要掌握以下三種類型的異常:

檢查性異常:

最具代表的檢查性異常是使用者錯誤或問題引起的異常,這是程式設計師無法預見的。例如要開啟一個不存在檔案時,一個異常就發生了,這些異常在編譯時不能被簡單地忽略。

執行時異常:

執行時異常是可能被程式設計師避免的異常。與檢查性異常相反,執行時異常可以在編譯時被忽略。

錯誤:

錯誤不是異常,而是脫離程式設計師控制的問題。錯誤在程式碼中通常被忽略。例如,當棧溢位時,一個錯誤就發生了,它們在編譯也檢查不到的。

Java異常的體系結構

Java把異常當作物件來處理,並定義一個基類java。lang。Throwable作為所有異常的超類。

在Java API中已經定義了許多異常類,這些異常類分為兩大類,

錯誤Error和異常Exception

Java異常層次結構圖如下圖所示:

寫了這麼久的業務連異常都不知道怎麼處理嗎?

在Java中,所有異常類的父類是Throwable類,Error類是error型別異常的父類,Exception類是exception型別異常的父類,RuntimeException類是所有執行時異常的父類,RuntimeException以外的並且繼承Exception的類是非執行時異常。

Error

:Error類物件由 Java 虛擬機器生成並丟擲,大多數錯誤與程式碼編寫者所執行的操作無關。例如,Java虛擬機器執行錯誤(Virtual MachineError),當JVM不再有繼續執行操作所需的記憶體資源時,將出現 OutOfMemoryError。這些異常發生時,Java虛擬機器(JVM)一般會選擇執行緒終止;還有發生在虛擬機器試圖執行應用時,如類定義錯誤(NoClassDefFoundError)、連結錯誤(LinkageError)。這些錯誤是不可查的,因為它們在應用程式的控制和處理能力之 外,而且絕大多數是程式執行時不允許出現的狀況。對於設計合理的應用程式來說,即使確實發生了錯誤,本質上也不應該試圖去處理它所引起的異常狀況。在Java中,錯誤通常是使用Error的子類描述。

Exception

:在Exception分支中有一個重要的子類RuntimeException(執行時異常),該型別的異常自動為你所編寫的程式定義ArrayIndexOutOfBoundsException(陣列下標越界)、NullPointerException(空指標異常)、ArithmeticException(算術異常)、MissingResourceException(丟失資源)、ClassNotFoundException(找不到類)等異常,這些異常是不檢查異常,程式中可以選擇捕獲處理,也可以不處理。這些異常一般是由程式邏輯錯誤引起的,程式應該從邏輯角度儘可能避免這類異常的發生;而RuntimeException之外的異常我們統稱為非執行時異常,型別上屬於Exception類及其子類,從程式語法角度講是必須進行處理的異常,如果不處理,程式就不能編譯透過。如IOException、SQLException等以及使用者自定義的Exception異常,一般情況下不自定義檢查異常。

Java 異常的處理機制

Java的異常處理本質上是

丟擲異常

捕獲異常

丟擲異常

:要理解丟擲異常,首先要明白什麼是異常情形(exception condition),它是指阻止當前方法或作用域繼續執行的問題。其次把異常情形和普通問題相區分,普通問題是指在當前環境下能得到足夠的資訊,總能處理這個錯誤。對於異常情形,已經無法繼續下去了,因為在當前環境下無法獲得必要的資訊來解決問題,你所能做的就是從當前環境中跳出,並把問題提交給上一級環境,這就是丟擲異常時所發生的事情。丟擲異常後,會有幾件事隨之發生。首先,是像建立普通的java物件一樣將使用new在堆上建立一個異常物件;然後,當前的執行路徑(已經無法繼續下去了)被終止,並且從當前環境中彈出對異常物件的引用。此時,異常處理機制接管程式,並開始尋找一個恰當的地方繼續執行程式,這個恰當的地方就是異常處理程式或者異常處理器,它的任務是將程式從錯誤狀態中恢復,以使程式要麼換一種方式執行,要麼繼續執行下去。

舉個簡單的例子,假使我們建立了一個學生物件Student的一個引用stu,在呼叫的時候可能還沒有初始化。所以在使用這個物件引用呼叫其他方法之前,要先對它進行檢查,可以建立一個代表錯誤資訊的物件,並且將它從當前環境中丟擲,這樣就把錯誤資訊傳播到更大的環境中。

if(stu == null){ throw new NullPointerException();}

捕獲異常

:在方法丟擲異常之後,執行時系統將轉為尋找合適的異常處理器(exception handler)。潛在的異常處理器是異常發生時依次存留在呼叫棧中的方法的集合。當異常處理器所能處理的異常型別與方法丟擲的異常型別相符時,即為合適的異常處理器。執行時系統從發生異常的方法開始,依次回查呼叫棧中的方法,直至找到含有合適異常處理器的方法並執行。當執行時系統遍歷呼叫棧而未找到合適的異常處理器,則執行時系統終止。同時,意味著Java程式的終止。

Java異常處理涉及到五個關鍵字,分別是:try、catch、finally、throw、throws。下面將驟一介紹,透過認識這五個關鍵字,掌握基本異常處理知識。

try

—— 用於監聽。將要被監聽的程式碼(可能丟擲異常的程式碼)放在try語句塊之內,當try語句塊內發生異常時,異常就被丟擲。

catch

—— 用於捕獲異常。catch用來捕獲try語句塊中發生的異常。

finally

—— finally語句塊總是會被執行。它主要用於回收在try塊裡開啟的物力資源(如資料庫連線、網路連線和磁碟檔案)。只有finally塊,執行完成之後,才會回來執行try或者catch塊中的return或者throw語句,如果finally中使用了return或者throw等終止方法的語句,則就不會跳回執行,直接停止。

throw

—— 用於丟擲異常。

throws

—— 用在方法簽名中,用於宣告該方法可能丟擲的異常。

專案中到底要怎麼去處理異常呢

小六六這邊分2種情況來說說,一種就是我們一般的後臺管理系統,一種是類似於支付系統的C端專案,再我的感覺中,它們對異常處理的細粒度是不一樣的。

一般的後臺管理系統的方式

一個異常列舉類,所有異常碼定義在這裡:

public enum BizExceptionEnum { APPLICATION_ERROR(1000, “網路繁忙,請稍後再試”), INVALID_USER(1001, “使用者名稱或密碼錯誤”), INVALID_REQ_PARAM(1002, “引數錯誤”), EXAM_NOT_FOUND(1003, “未查到考試資訊”), ; BizExceptionEnum(Integer errorCode, String errorMsg) { this。errorCode = errorCode; this。errorMsg = errorMsg; } private final Integer errorCode; private final String errorMsg; // get……}

一個業務異常類:

public class BizException extends RuntimeException { private final BizExceptionEnum bizExceptionEnum; public BizException(BizExceptionEnum bizExceptionEnum) { super(bizExceptionEnum。getErrorMsg()); this。bizExceptionEnum = bizExceptionEnum; } public BizExceptionEnum getBizExceptionEnum() { return bizExceptionEnum; }}

一個全域性的異常處理類:

@RestControllerAdvicepublic class GlobalHandler { private final Logger logger = LoggerFactory。getLogger(GlobalHandler。class); @ExceptionHandler(MethodArgumentNotValidException。class) public Result exceptionHandler(MethodArgumentNotValidException e) { Result result = new Result(BizExceptionEnum。INVALID_REQ_PARAM。getErrorCode(), BizExceptionEnum。INVALID_REQ_PARAM。getErrorMsg()); logger。error(“req params error”, e); return result; } @ExceptionHandler(BizException。class) public Result exceptionHandler(BizException e) { BizExceptionEnum exceptionEnum = e。getBizExceptionEnum(); Result result = new Result(exceptionEnum。getErrorCode(), exceptionEnum。getErrorMsg()); logger。error(“business error”, e); return result; } @ExceptionHandler(value = Exception。class) public Result exceptionHandler(Exception e) { Result result = new Result(BizExceptionEnum。APPLICATION_ERROR。getErrorCode(), BizExceptionEnum。APPLICATION_ERROR。getErrorMsg()); logger。error(“application error”, e); return result; }

其中Result類的定義:

public class Result { private Boolean success; private Integer errorCode; private String errorMsg; private T data; public Result(T data) { this(true, null, null, data); } public Result(Integer errorCode, String errorMsg) { this(false, errorCode, errorMsg, null); } public Result(Boolean success, Integer errorCode, String errorMsg, T data) { this。success = success; this。errorCode = errorCode; this。errorMsg = errorMsg; this。data = data; } // get set……}

示例Controller類:

@RestController@RequestMapping(“/json/exam”)public class ExamController { @Autowired private IExamService examService; @PostMapping(“/getExamList”) public Result> getExamList(@Validated @RequestBody GetExamListReqVo reqVo, @AuthenticationPrincipal UserDetails userDetails) throws IOException { List resVos = examService。getExamList(reqVo, userDetails); Result> result = new Result(resVos); return result; }}

其中Result類的定義:

public class Result { private Boolean success; private Integer errorCode; private String errorMsg; private T data; public Result(T data) { this(true, null, null, data); } public Result(Integer errorCode, String errorMsg) { this(false, errorCode, errorMsg, null); } public Result(Boolean success, Integer errorCode, String errorMsg, T data) { this。success = success; this。errorCode = errorCode; this。errorMsg = errorMsg; this。data = data; } // get set……}

示例Controller類:

@RestController@RequestMapping(“/json/exam”)public class ExamController { @Autowired private IExamService examService; @PostMapping(“/getExamList”) public Result> getExamList(@Validated @RequestBody GetExamListReqVo reqVo, @AuthenticationPrincipal UserDetails userDetails) throws IOException { List resVos = examService。getExamList(reqVo, userDetails); Result> result = new Result(resVos); return result; }}

示例Service類:

@Servicepublic class IExamServiceImpl implements IExamService { @Autowire private ManualMicrowebsiteMapper microwebsiteMapper; @Override public List getExamList(GetExamListReqVo reqVo, UserDetails userDetails) throws IOException { List examEntities = microwebsiteMapper。select(reqVo。getExamType(), userDetails。getUsername()); // 按照業務的定義要求,此處考試列表必須不為空,一旦為空,則說明後臺配置有誤或其它未知原因,這種情況視為一種業務異常 if (examEntities。isEmpty()) { // 未查到考試資訊,丟擲相應的業務異常 throw new BizException(BizExceptionEnum。EXAM_NOT_FOUND); } // 此處程式碼還有其它各類異常丟擲…… List resVos = examEntities。stream()。map(examEntity -> { GetExamListResVo resVo = new GetExamListResVo(); BeanUtils。copyProperties(examEntity, resVo); return resVo; })。collect(toList()); return resVos; }}

C端專案的例子

其實,C端專案大體和上面說一致的,但是我們一般都是微服務進行開發,那麼我們應該一開始就給每個服務的業務異常碼返回一個範圍,這樣就能從請求的源頭就能知道錯誤的點在哪個系統,這是第一個點吧

第二個,其實對於每個微服務,和上面的異常處理上一樣的,但是我想說的是對於上面處理的Service,我們應該對裡面的業務異常更加細膩的去處理,因為我們只是丟擲了一些我們能預判到的一些業務異常,但是一些比如JSON轉換異常等等異常,我們要到最外層去處理,但是最外層也只是把這個異常轉換成大異常了,這樣就是說對於C端專案來說,這樣異常的力度,應該不是不夠的,我們應該再細分一下,就是儘可能的把一些可能的異常轉換成我們業務異常,這樣的話,我們程式碼的健壯性就會好很多,而且很多的異常展示給使用者的文案也是可以統一轉換的。我們來看下面一個Service解綁的業務的例子吧!

// 1根據傳入條件判斷該派安盈使用者存在,狀態是normalThirdAcct thirdAcct = null;try { thirdAcct = thirdAcctRelationService。getNormalThirdAcct(chId, userType, thirdUserId, payMethod, chAccountId);} catch (DataAccessException e) { throw new UIDataAccessException(e。getCode(), e。getMessage(), “fail。thirdAcct。queryfail”,e);}//1。1不存在說明已經刪除過了if (ObjectUtil。isNull(thirdAcct)) { return Boolean。TRUE;}//1。2存在,證明等待刪除// 2。獲取賬號的渠道用於調取派安盈介面ChAccount chAccount = null;try { chAccount = chAccountRelationService。getChAccount(appId, chId, payMethod, chAccountId);} catch (DataAccessException e) { //di裝bi throw new UIDataAccessException(e。getCode(), e。getMessage(), “fail。chAccount。miss”,e);}if (ObjectUtil。isNull(chAccount)) { throw new UIDataAccessException(“fail。chAccount。miss”, “,獲取渠道賬號資訊失敗”, “fail。chAccount。miss”);}ChannelRelationService channelService = this。getChannelRelationService(chId);if (channelService == null) { //沒有相關渠道 throw new UIDataAccessException(“fail。channel。unfound”, “找不到相關渠道的service,chId:”+chId, “fail。channel。unfo```//解綁try { channelService。unbind(chAccount,thirdAcct);} catch (DataAccessException e) { throw new UIDataAccessException(e。getCode(), e。getMessage(), ”fail。thirdChannel。bindFail“,e);}//4。解綁,注意這些多個聯合元件的,要根據條件來解綁的,try { thirdAcctRelationService。unbindThirdAcct(thirdAcct);} catch (DataAccessException e) { throw new UIDataAccessException(e。getCode(), e。getMessage(), ”fail。dbthirdacct。bindFail“,e);}

我們可以看到,一個業務可能拆分多個子業務,那麼每個子業務都有可能拋不同的異常,你要再不同的子業務中把它們的業務轉換成ui的異常,只要你的系統夠完善,那麼意外的異常就會非常少,這樣下去你的系統會越來越穩定的。

結束

好了,今天的分享就到這了,可能很多小夥伴看了會覺得沒啥東西,那是因為你沒有體驗過一個C端產品的嚴謹性,如果僅僅是一個後臺管理,確實是不必要這樣的,但是對於面向使用者的產品,我覺得異常處理的好壞,決定了你這個產品的系統質量!

作者:六脈神劍

連結:https://juejin。cn/post/7031745150648844301

Top