軟件開發過程當中不免遇到各類的BUG,各類的異常,一直就是在解決異常的路上永不停歇,若是你的代碼中再出現try(){...}catch(){...}finally{...}
代碼塊,你還有心情看下去嗎?本身不以爲噁心嗎?java
冗餘的代碼每每回喪失寫代碼的動力,天天搬磚似的寫代碼,真的很難受。今天這篇文章教你如何去掉滿屏的try(){...}catch(){...}finally{...}
,解放你的雙手。程序員
本文基於的Spring Boot的版本是2.3.4.RELEASE
。web
早在Spring 3.x
就已經提出了@ControllerAdvice
,能夠與@ExceptionHandler
、@InitBinder
、@ModelAttribute
等註解註解配套使用,這幾個此處就再也不詳細解釋了。spring
這幾個註解小眼一瞟只有@ExceptionHandler
與異常有關啊,翻譯過來就是異常處理器
。其實異常的處理能夠分爲兩類,分別是局部異常處理
和全局異常處理
。json
局部異常處理
:@ExceptionHandler
和@Controller
註解搭配使用,只有指定的controller層出現了異常纔會被@ExceptionHandler
捕獲到,實際生產中怕是有成百上千個controller了吧,顯然這種方式不合適。後端
全局異常處理
:既然局部異常處理不合適了,天然有人站出來解決問題了,因而就有了@ControllerAdvice
這個註解的橫空出世了,@ControllerAdvice
搭配@ExceptionHandler
完全解決了全局統一異常處理。固然後面還出現了@RestControllerAdvice
這個註解,其實就是@ControllerAdvice
和@ResponseBody
結晶。緩存
Java中的異常就不少,更別說Spring Boot中的異常了,這裏再也不根據傳統意義上Java的異常進行分類了,而是按照controller
進行分類,分爲進入controller前的異常
和業務層的異常
,以下圖:markdown
進入controller以前異常通常是javax.servlet.ServletException
類型的異常,所以在全局異常處理的時候須要統一處理。幾個常見的異常以下:app
NoHandlerFoundException
:客戶端的請求沒有找到對應的controller,將會拋出404
異常。HttpRequestMethodNotSupportedException
:若匹配到了(匹配結果是一個列表,不一樣的是http方法不一樣,如:Get、Post等),則嘗試將請求的http方法與列表的控制器作匹配,若沒有對應http方法的控制器,則拋該異常HttpMediaTypeNotSupportedException
:而後再對請求頭與控制器支持的作比較,好比content-type
請求頭,若控制器的參數簽名包含註解@RequestBody
,可是請求的content-type
請求頭的值沒有包含application/json
,那麼會拋該異常(固然,不止這種狀況會拋這個異常)MissingPathVariableException
:未檢測到路徑參數。好比url爲:/user/{userId},參數簽名包含@PathVariable("userId")
,當請求的url爲/user,在沒有明肯定義url爲/user的狀況下,會被斷定爲:缺乏路徑參數在統一異常處理以前其實還有許多東西須要優化的,好比統一結果返回的形式。固然這裏再也不細說了,不屬於本文範疇。框架
統一異常處理很簡單,這裏之前後端分離的項目爲例,步驟以下:
@RestControllerAdvice
這一個註解,或者同時標註@ControllerAdvice
和@ResponseBody
這兩個註解。@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失效
、過時
等等異常,若是整合了其餘的框架,還要注意這些框架拋出的異常,好比Shiro
,Spring Security
等等框架。
有些朋友可能疑惑了,若是我同時捕獲了父類和子類,那麼到底可以被那個異常處理器捕獲呢?好比Exception
和ServiceException
。
此時可能就疑惑了,這裏先揭曉一下答案,固然是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進階
便可獲取,目錄以下: