滿屏的try-catch,你不瘮得慌?

目錄

  • 前言
  • Spring Boot 版本
  • 全局統一異常處理的前世此生
  • Spring Boot的異常如何分類?
  • 如何統一異常處理?
  • 異常匹配的順序是什麼?
  • 總結

前言

軟件開發過程當中不免遇到各類的BUG,各類的異常,一直就是在解決異常的路上永不停歇,若是你的代碼中再出現try(){...}catch(){...}finally{...}代碼塊,你還有心情看下去嗎?本身不以爲噁心嗎?java

冗餘的代碼每每回喪失寫代碼的動力,天天搬磚似的寫代碼,真的很難受。今天這篇文章教你如何去掉滿屏的try(){...}catch(){...}finally{...},解放你的雙手。程序員

Spring Boot 版本

本文基於的Spring Boot的版本是2.3.4.RELEASEweb

全局統一異常處理的前世此生

早在Spring 3.x就已經提出了@ControllerAdvice,能夠與@ExceptionHandler@InitBinder@ModelAttribute 等註解註解配套使用,這幾個此處就再也不詳細解釋了。spring

這幾個註解小眼一瞟只有@ExceptionHandler與異常有關啊,翻譯過來就是異常處理器其實異常的處理能夠分爲兩類,分別是局部異常處理全局異常處理json

局部異常處理@ExceptionHandler@Controller註解搭配使用,只有指定的controller層出現了異常纔會被@ExceptionHandler捕獲到,實際生產中怕是有成百上千個controller了吧,顯然這種方式不合適。後端

全局異常處理:既然局部異常處理不合適了,天然有人站出來解決問題了,因而就有了@ControllerAdvice這個註解的橫空出世了,@ControllerAdvice搭配@ExceptionHandler完全解決了全局統一異常處理。固然後面還出現了@RestControllerAdvice這個註解,其實就是@ControllerAdvice@ResponseBody結晶。緩存

Spring Boot的異常如何分類?

Java中的異常就不少,更別說Spring Boot中的異常了,這裏再也不根據傳統意義上Java的異常進行分類了,而是按照controller進行分類,分爲進入controller前的異常業務層的異常,以下圖:markdown

進入controller以前異常通常是javax.servlet.ServletException類型的異常,所以在全局異常處理的時候須要統一處理。幾個常見的異常以下:app

  1. NoHandlerFoundException:客戶端的請求沒有找到對應的controller,將會拋出404異常。
  2. HttpRequestMethodNotSupportedException:若匹配到了(匹配結果是一個列表,不一樣的是http方法不一樣,如:Get、Post等),則嘗試將請求的http方法與列表的控制器作匹配,若沒有對應http方法的控制器,則拋該異常
  3. HttpMediaTypeNotSupportedException:而後再對請求頭與控制器支持的作比較,好比content-type請求頭,若控制器的參數簽名包含註解@RequestBody,可是請求的content-type請求頭的值沒有包含application/json,那麼會拋該異常(固然,不止這種狀況會拋這個異常)
  4. MissingPathVariableException:未檢測到路徑參數。好比url爲:/user/{userId},參數簽名包含@PathVariable("userId"),當請求的url爲/user,在沒有明肯定義url爲/user的狀況下,會被斷定爲:缺乏路徑參數

如何統一異常處理?

在統一異常處理以前其實還有許多東西須要優化的,好比統一結果返回的形式。固然這裏再也不細說了,不屬於本文範疇。框架

統一異常處理很簡單,這裏之前後端分離的項目爲例,步驟以下

  1. 新建一個統一異常處理的一個類
  2. 類上標註@RestControllerAdvice這一個註解,或者同時標註@ControllerAdvice@ResponseBody這兩個註解。
  3. 在方法上標註@ExceptionHandler註解,而且指定須要捕獲的異常,能夠同時捕獲多個。

下面是做者隨便配置一個demo,以下:

/** * 全局統一的異常處理,簡單的配置下,根據本身的業務要求詳細配置 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {


    /** * 重複請求的異常 * @param ex * @return */
    @ExceptionHandler(RepeatSubmitException.class)
    public ResultResponse onException(RepeatSubmitException ex){
        //打印日誌
        log.error(ex.getMessage());
        //todo 日誌入庫等等操做

        //統一結果返回
        return new ResultResponse(ResultCodeEnum.CODE_NOT_REPEAT_SUBMIT);
    }


    /** * 自定義的業務上的異常 */
    @ExceptionHandler(ServiceException.class)
    public ResultResponse onException(ServiceException ex){
        //打印日誌
        log.error(ex.getMessage());
        //todo 日誌入庫等等操做

        //統一結果返回
        return new ResultResponse(ResultCodeEnum.CODE_SERVICE_FAIL);
    }


    /** * 捕獲一些進入controller以前的異常,有些4xx的狀態碼統一設置爲200 * @param ex * @return */
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class, HttpMediaTypeNotAcceptableException.class, MissingPathVariableException.class, MissingServletRequestParameterException.class, ServletRequestBindingException.class, ConversionNotSupportedException.class, TypeMismatchException.class, HttpMessageNotReadableException.class, HttpMessageNotWritableException.class, MissingServletRequestPartException.class, BindException.class, NoHandlerFoundException.class, AsyncRequestTimeoutException.class})
    public ResultResponse onException(Exception ex){
        //打印日誌
        log.error(ex.getMessage());
        //todo 日誌入庫等等操做

        //統一結果返回
        return new ResultResponse(ResultCodeEnum.CODE_FAIL);
    }
}
複製代碼

注意上面的只是一個例子,實際開發中還有許多的異常須要捕獲,好比TOKEN失效過時等等異常,若是整合了其餘的框架,還要注意這些框架拋出的異常,好比ShiroSpring Security等等框架。

異常匹配的順序是什麼?

有些朋友可能疑惑了,若是我同時捕獲了父類和子類,那麼到底可以被那個異常處理器捕獲呢?好比ExceptionServiceException

此時可能就疑惑了,這裏先揭曉一下答案,固然是ServiceException的異常處理器捕獲了,精確匹配,若是沒有ServiceException的異常處理器纔會輪到它的父親父親沒有才會到祖父。總之一句話,精準匹配,找那個關係最近的。

爲何呢?這可不是憑空瞎說的,源碼爲證,出處org.springframework.web.method.annotation.ExceptionHandlerMethodResolver#getMappedMethod,以下:

@Nullable
	private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
		List<Class<? extends Throwable>> matches = new ArrayList<>();
    //遍歷異常處理器中定義的異常類型
		for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
      //是不是拋出異常的父類,若是是添加到集合中
			if (mappedException.isAssignableFrom(exceptionType)) {    
        //添加到集合中
				matches.add(mappedException);  
			}
		}
    //若是集合不爲空,則按照規則進行排序
		if (!matches.isEmpty()) {
			matches.sort(new ExceptionDepthComparator(exceptionType));
      //取第一個
			return this.mappedMethods.get(matches.get(0));
		}
		else {
			return null;
		}
	}
複製代碼

在初次異常處理的時候會執行上述的代碼找到最匹配的那個異常處理器方法,後續都是直接從緩存中(一個Map結構,key是異常類型,value是異常處理器方法)。

彆着急,上面代碼最精華的地方就是對matches進行排序的代碼了,咱們來看看ExceptionDepthComparator這個比較器的關鍵代碼,以下:

//遞歸調用,獲取深度,depth值越小越精準匹配
private int getDepth(Class<?> declaredException, Class<?> exceptionToMatch, int depth) {
    //若是匹配了,返回
		if (exceptionToMatch.equals(declaredException)) {
			// Found it!
			return depth;
		}
		// 遞歸結束的條件,最大限度了
		if (exceptionToMatch == Throwable.class) {
			return Integer.MAX_VALUE;
		}
    //繼續匹配父類
		return getDepth(declaredException, exceptionToMatch.getSuperclass(), depth + 1);
	}
複製代碼

精髓全在這裏了,一個遞歸搞定,計算深度,depth初始值爲0。值越小,匹配度越高越精準。

總結

全局異常的文章萬萬千,可以講清楚的能有幾篇呢?只出最精的文章,作最野的程序員,若是以爲不錯的,關注分享走一波,謝謝支持!!!

另外做者的第一本PDF書籍已經整理好了,由淺入深的詳細介紹了Mybatis基礎以及底層源碼,有須要的朋友公衆號碼猿技術專欄回覆關鍵詞Mybatis進階便可獲取,目錄以下:

相關文章
相關標籤/搜索