亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中Service層異常該怎么處理

發布時間:2022-02-24 10:12:20 來源:億速云 閱讀:912 作者:iii 欄目:開發技術

這篇文章主要介紹“Java中Service層異常該怎么處理”,在日常操作中,相信很多人在Java中Service層異常該怎么處理問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java中Service層異常該怎么處理”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

處理錯誤是為了寫出正確的程序

到底怎么算“正確”呢?是要由解決的問題決定的。問題不同,解決方案就不同。

比如,一個web接口接受用戶的請求,這個請求需要傳入一個參數“年齡”,也許業務要求這個字段應該是個0~150之間的整數。如果用戶輸入的是個字符串或者負數就肯定不被接受。一般在后端的某個地方都會做輸入的合法性檢查,檢查不過就拋異常。但是歸根到底這個問題的“正確”解決方法總是要以某種形式提示用戶。而提示用戶是某種前端工作,這就要看這個界面到底是appH5 + ajax還是類似于jsp那樣的服務器產生的界面。不管哪種,你需要根據需求去”設計一個修復錯誤“的流程。比如一個常見的流程需要后端拋異常,然后一路到某個集中處理錯誤的代碼,將其轉換為某個HTTP的錯誤(某個特定業務錯誤碼)提供給前端,前端再去做”提示“。如果用戶輸入了非法的請求,從邏輯上后端都沒法自己修復,這是個“正確”的策略。

換一個例子,比如用戶想上傳一個頭像,后端將圖片發給某個云存儲,結果云存儲報500錯誤。怎么辦呢?你可能想到了重試幾次,因為也許問題僅僅是臨時的網絡抖動而已,重試就可以正常執行。但如果重試多次無效。如果做系統時設計了某種熱備方案,那么就可能改為發到另外一個服務器上。“重試”和“使用備份的依賴”都是“立刻處理“。

但如果重試無效,所有的備份服務也無效,那么也許就能像上面那樣把錯誤拋給前端,提示用戶“服務器開小差”。從這個方案很容易看出來,你想把錯誤拋到哪里是因為那個catch的地方是處理問題最方便的地方。一個問題的解決方案可能要幾個不同的錯誤處理組合起來才能辦到。

另外一個例子,你的程序拋了一個NPE。這一般就是程序員的bug——要不就是程序員想要表達一個東西”沒有“,結果在后續處理中忘了判斷是否為null;要不就是在寫代碼時覺得100%不可能為null的地方出現了一個null。不管哪種情況,這個錯誤用戶總會看到一個很含糊的報錯信息,這遠遠不夠。“正確”的辦法是程序員自己能盡快發現它,并盡快修復。要做到這一點,需要監控系統不斷的爬log,把問題報警出來。而不是等到用戶找客服來吐槽。

再換一個例子,比如你的后端程序突然OOM,掛了。掛的程序是沒法恢復自己的。要做到“正確”就必須得在服務之外的容器考慮這個問題。比如你的服務跑在k8s上,他們會監控你程序的狀態,然后重新啟動新的服務實例以彌補掛掉的服務,還得調整流量,把去往掛掉服務的流量切掉,重新換到新的實例上。這里的恢復因為跨系統所以不能僅僅用異常實現,但是道理是一樣的。但光靠重啟就是“正確”的嗎?如果服務是完全無狀態的,問題不大。但是如果是有狀態的,部分用戶數據可能就會被執行一半的請求搞亂套。因此重啟時要留意先“恢復數據到合法狀態”。這又回到了你需要知道怎么樣才是“正確”的做法。只依靠簡單的語法功能是不能無腦解決這個事的。

  • 我們可以推廣下,一個工作線程的“外部容器“是管理工作線程的“master”。一個網絡請求的“外部容器”是一個web server。一個用戶進程的“外部容器”是操作系統。Erlang把這種supervisor-worker的機制融入到語言的設計中。

(推薦微課:Java微課)

Web程序之所以很大程度上能夠把異常拋給頂層,主要由于3個原因:

  • 請求來自于前端,對于因為用戶請求有誤(數據合法性、權限、用戶上下文狀態)造成的問題,最終大概率只能告訴用戶。因此拋異常到一個集中處理錯誤的地方,把異常轉換為某個業務錯誤碼的方法是合理的。

  • 后端服務一般都是無狀態的。這也是互聯網系統設計的一般性原則。無狀態就意味著可以隨意重啟。對于用戶的數據因為下一條一般情況下不會出問題。

  • 后端對數據的修改依賴DB的事務。因此一個改了一半的沒提交的事務是不會造成副副作用。

但你要清楚上面這3條并不是總是成立的。總會存在一些處理邏輯并非完全無狀態,也并不是所有的數據修改都能用一個事務保護。尤其要注意對微服務的調用,對內存狀態的修改是沒有事務保護的,一不留神就會出現搞亂用戶數據的問題。比如:

 try {
   int res1 = doStep1();
   this.statusVar1 += res1;
   int res2 = doStep2();
   this.statusVar2 += res2;
   int res3 = doStep3(); // throw an exception
   this.statusVar3 = statusVar1 + statusVar2 + res3;
} catch ( ...) { 
   // ...
}

先假設this.statusVar1, this.statusVar2, this.statusVar3之間需要維護某種不變的約束(invariant)。然后執行這段代碼時,如果在doStep3那拋出一個異常下面對statusVar3的賦值就不會執行。這時如果不能將statusVar1statusVar2的修改rollback回去,就會造成數據違反約束的問題。而程序員一般是很難直接發現這個數據被改壞了。而壞掉的數據可能會偷偷的導致其他依賴這個數據的代碼邏輯出錯(比如原本應該給積分的,結果卻沒給)。而這種錯誤一般非常難調查,從大量數據里找到不正確的那一小撮是相當困難的事。

比起上面這段更難搞得定的是這樣的代碼:

// controller
void controllerMethod(/* some params*/) {
  try {
    return svc.doWorkAndGetResult(/* some params*/);
  } catch (Exception e) {
    return ErrorJsonObject.of(e);
  }
}


// class svc
void doWorkAndGetResult(/* some params*/) {
    int res1 = otherSvc1.doStep1(/* some params */);
    this.statusVar1 += res1;
    int res2 = otherSvc2.doStep2(/* some params */);
    this.statusVar2 += res2;
    int res3 = otherSvc3.doStep3(/* some params */);
    this.statusVar3 = statusVar1 + statusVar2 + res3;
    return SomeResult.of(this.statusVar1, this.statusVar2, this.statusVar3);
}

這段代碼的可怕之處在于,你在寫的時候可能會以為doStep1~3這種東西即使拋異常,也能被Controller里的catch。在svc這層是不用處理任何異常的,因此不寫try……catch是天經地義的。但實際上doStep1doStep2doStep3任何一個拋異常都會造成svc的數據狀態不一致。甚至你一開始都可以通過文檔或者其他溝通方式確定doStep1doStep2doStep3一開始都是必然可以成功,不會拋錯的,因此你寫的代碼一開始是對的。但是你可能無法控制他們的實現(比如他們是另外一個團隊開發的lib提供的),而他們的實現可能會改成會拋錯。你的代碼可能在完全不自知的情況下從“不會出問題”變成了“可能出問題”…… 更可怕的是類似于這樣的代碼是不能正確工作的:

void doWorkAndGetResult(/* some params*/) {
    try {
       int res1 = otherSvc1.doStep1(/* some params */);
       this.statusVar1 += res1;
       int res2 = otherSvc2.doStep2(/* some params */);
       this.statusVar2 += res2;
       int res3 = otherSvc3.doStep3(/* some params */);
       this.statusVar3 = statusVar1 + statusVar2 + res3;
       return SomeResult.of(this.statusVar1, this.statusVar2, this.statusVar3);
   } catch (Exception e) {
     // do rollback
   }
}

你可能以為這樣就會處理好數據rollback了,甚至你會覺得這種代碼非常優雅。但是實際上doStep1~3每一個地方拋錯,rollback的代碼都不一樣。你必須得這么寫:

void doWorkAndGetResult(/* some params*/) {
    int res1, res2, res3;
    try {
       res1 = otherSvc1.doStep1(/* some params */);
       this.statusVar1 += res1;
    } catch (Exception e) {
       throw e;
    }


    try {
      res2 = otherSvc2.doStep2(/* some params */);
      this.statusVar2 += res2;
    } catch (Exception e) {
      // rollback statusVar1
      this.statusVar1 -= res1;
      throw e;
    }

  
    try {
      res3 = otherSvc3.doStep3(/* some params */);
      this.statusVar3 = statusVar1 + statusVar2 + res3;
    } catch (Exception e) {
      // rollback statusVar1 & statusVar2
      this.statusVar1 -= res1;
      this.statusVar2 -= res2;
      throw e;
   } 
}

這才是能得到正確結果的代碼——在任何地方出現錯誤都能維護數據一致性。優雅嗎?看起來很丑。這甚至比goif err != nil還丑。但如果一定要在正確性和優雅性上作出取舍,我會毫不猶豫的選擇前者。作為程序員是不能直接認為拋異常可以解決任何問題的,你必須學會寫出有正確邏輯的程序,哪怕很難,并且看起來很丑。為了達成很高的正確性,你不能總是把自己大部分注意力放在“一切都OK的流程上“,而把錯誤看作是可以隨便搞一下的工作,或者簡單的相信exception可以自動搞定一切。

到此,關于“Java中Service層異常該怎么處理”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

个旧市| 闻喜县| 芦溪县| 正宁县| 乌鲁木齐县| 翼城县| 禄丰县| 浦北县| 建平县| 井研县| 六枝特区| 通山县| 顺义区| 万源市| 饶河县| 通海县| 昌图县| 内江市| 刚察县| 襄汾县| 南漳县| 望谟县| 志丹县| 商南县| 千阳县| 松潘县| 屯门区| 嵊州市| 睢宁县| 宾川县| 迭部县| 同仁县| 法库县| 德格县| 双城市| 崇左市| 卓尼县| 孙吴县| 武城县| 辉县市| 吴堡县|