Spring Boot 統一異常這樣處理和剖析,安否?
話說異常
「欲渡黃河冰塞川,將登太行雪滿天」
,無論生活還是計算機世界難免發生異常,上一篇文章RESTful API 返回統一JSON資料格式 說明了統一返回的處理,這是請求一切正常的情形;這篇文章將說明如何統一處理異常,以及其背後的實現原理,老套路,先實現,後說明原理,有了上一篇文章的鋪底,相信,理解這篇文章就駕輕就熟了
實現
新建業務異常
新建 BusinessException.class 類表示業務異常,注意這是一個 Runtime 異常
@Data @AllArgsConstructor public final class BusinessException extends RuntimeException { private String errorCode; private String errorMsg; }
新增統一異常處理靜態方法
在 CommonResult 類中新增靜態方法 errorResult 用於接收異常碼和異常訊息:
public static <T> CommonResult<T> errorResult(String errorCode, String errorMsg){ CommonResult<T> commonResult = new CommonResult<>(); commonResult.errorCode = errorCode; commonResult.errorMsg = errorMsg; commonResult.status = -1; return commonResult; }
配置
同樣要用到 @RestControllerAdvice
註解,將統一異常新增到配置中:
@RestControllerAdvice("com.example.unifiedreturn.api") static class UnifiedExceptionHandler{ @ExceptionHandler(BusinessException.class) public CommonResult<Void> handleBusinessException(BusinessException be){ return CommonResult.errorResult(be.getErrorCode(), be.getErrorMsg()); } }
三部搞定,到這裡無論是 Controller 還是 Service 中,只要丟擲 BusinessException, 我們都會返回給前端一個統一資料格式
測試
將 UserController 中的方法進行改造,直接丟擲異常:
@GetMapping("/{id}")
public UserVo getUserById(@PathVariable Long id){
throw new BusinessException("1001", "根據ID查詢使用者異常");
}
瀏覽器中輸入: http://localhost:8080/users/1
在 Service 中丟擲異常:
@Service
public class UserServiceImpl implements UserService {
/**
* 根據使用者ID查詢使用者
*
* @param id
* @return
*/
@Override
public UserVo getUserById(Long id) {
throw new BusinessException("1001", "根據ID查詢使用者異常");
}
}
執行是得到同樣的結果,所以我們儘可能的丟擲異常吧 (作為一個程式猿這種心理很可拍)
解剖實現過程
解剖這個過程是相當糾結的,為了更好的說(yin)明(wei)問(wo)題(lan),我要說重中之重了,真心希望看該文章的童鞋自己去案發現場發現線索
還是在 WebMvcConfigurationSupport 類中例項化了 HandlerExceptionResolver Bean
@Bean
public HandlerExceptionResolver handlerExceptionResolver() {
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
addDefaultHandlerExceptionResolvers(exceptionResolvers);
}
extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}
和上一篇文章一毛一樣的套路,ExceptionHandlerExceptionResolver 實現了 InitializingBean 介面,重寫了 afterPropertiesSet 方法:
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBodyAdvice beans
initExceptionHandlerAdviceCache();
...
}
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(adviceBeans);
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
// 重點看這個構造方法
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
}
重點看上面我用註釋標記的構造方法,程式碼很好懂,仔細看看吧,其實就是篩選出我們用 @ExceptionHandler 註解標記的方法並放到集合當中,用於後續全域性異常捕獲的匹配
/**
* A constructor that finds {@link ExceptionHandler} methods in the given type.
* @param handlerType the type to introspect
*/
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
}
/**
* Extract exception mappings from the {@code @ExceptionHandler} annotation first,
* and then as a fallback from the method signature itself.
*/
@SuppressWarnings("unchecked")
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
List<Class<? extends Throwable>> result = new ArrayList<>();
detectAnnotationExceptionMappings(method, result);
if (result.isEmpty()) {
for (Class<?> paramType : method.getParameterTypes()) {
if (Throwable.class.isAssignableFrom(paramType)) {
result.add((Class<? extends Throwable>) paramType);
}
}
}
if (result.isEmpty()) {
throw new IllegalStateException("No exception types mapped to " + method);
}
return result;
}
private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
Assert.state(ann != null, "No ExceptionHandler annotation");
result.addAll(Arrays.asList(ann.value()));
}
到這裡,我們用 @RestControllerAdvice
和 @ExceptionHandler
註解就會被 Spring 掃描到上下文,供我們使用
讓我們回到你最熟悉的呼叫的入口 DispatcherServlet 類的 doDispatch 方法:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
...
// 當請求發生異常,該方法會通過 catch 捕獲異常
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 呼叫該方法分析捕獲的異常
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
...
}
接下來,我們來看 processDispatchResult 方法,這裡只要展示呼叫棧你就會眼前一亮了,又是為了返回統一格式資料:
總結
上一篇文章的返回統一資料格式是基礎,當異常情況發生時,只不過需要將異常資訊提取出來。本文主要為了說明問題,剖析原理,好多地方設計方式是不可取,比如我們最好將異常封裝在一個 Enum 類,通過 enum 物件丟擲異常等,如果你用到這些,去完善你的設計方案吧
回覆 「demo」,開啟連結,檢視資料夾 「unifiedreturn」下內容,獲取完整程式碼,更好閱讀體驗:
https://fraseryu.github.io/2019/08/09/ru-he-tong-yi-chu-li-yi-chang-bing-fan-hui-tong-yi-ge-shi/
附加說明
之前看到的一本書對異常的分類讓我印象深刻,在此摘錄一小段分享給大家:
結合出國旅行的例子說明異常分類:
- 機場地震,屬於不可抗力,對應異常分類中的
Error
,在制訂出行計劃時,根本不需要把這個部分的異常考慮進去- 堵車屬於
checked
異常,應對這種異常,我們可以提前出發,或者改簽機票。而飛機延誤異常,雖然也需要 check,但我們無能為力,只能持續關注航班動態- 沒有帶護照,明顯屬於可
提前預測的異常
,只要出發前檢查即可避免;去機場路上車子拋錨,這個異常是突發的,雖然難以預料,但是必須處理,屬於需要捕捉的異常
,可以通過更換交通工具;應對檢票機器故障屬於可透出異常
,交由航空公司處理,我們無須關心
靈魂追問
- 這兩篇文章,你學到了哪些設計模式?
- 你能熟練的使用反射嗎?當看原始碼是會看到很多反射的應用
- 你瞭解 Spring CGLIB 嗎?它的工作原理是什麼?
提高效率工具
JSON-Viewer
JSON-Viewer 是 Chrome 瀏覽器的外掛,用於快速解析及格式化 json 內容,在 Chrome omnibox(多功能輸入框)輸入json-viewer + TAB
,將 json 內容拷貝進去,然後輸入回車鍵,將看到結構清晰的 json 資料,同時可以自定義主題
另外,前端人員開啟開發者工具,雙擊請求連結,會自動將 response 中的 json 資料解析出來,非常方便
推薦閱讀
- 只會用 git pull ?有時候你可以嘗試更優雅的處理方式
- 雙親委派模型:大廠高頻面試題,輕鬆搞定
- 面試還不知道BeanFactory和ApplicationContext的區別?
- 如何設計好的RESTful API
- 程式猿為什麼要看原始碼?
歡迎持續關注公眾號:「日拱一兵」
- 前沿 Java 技術乾貨分享
- 高效工具彙總 | 回覆「工具」
- 面試問題分析與解答
- 技術資料領取 | 回覆「資料」
以讀偵探小說思維輕鬆趣味學習 Java 技術棧相關知識,本著將複雜問題簡單化,抽象問題具體化和圖形化原則逐步分解技術問題,技術持續更新,請持續關注......
相關推薦
Spring Boot 統一異常這樣處理和剖析,安否?
話說異常 「欲渡黃河冰塞川,將登太行雪滿天」,無論生活還是計算機世界難免發生異常,上一篇文章RESTful API 返回統一JSON資料格式 說明了統一返回的處理,這是請求一切正常的情形;這篇文章將說明如何統一處理異常,以及其背後的實現原理,老套路,先實現,後說明原理,有了上一篇文章的鋪底,相信,理解這篇文章
spring boot AOP和spring boot統一異常處理
一,spring AOPspring boot使用AOP,程式碼如下,程式碼比較簡單就不細說了,直接上程式碼,可以使用AOP做日誌處理package com.qwrt.fire.sensor.aop; import com.alibaba.fastjson.JSONArra
Spring Boot? 統一異常處理
xtend import put itl ava advice efault ges spring 效果區: 代碼區: package com.wls.integrateplugs.exception.dto; public class ErrorI
spring boot 統一異常處理
res status fin erro throwable instance 拋出異常 方案 let 需求源自於任何一個業務的編寫總會有各種各樣的條件判斷,需要時時手動拋出異常,又希望讓接口返回友好的錯誤信息。 spring boot提供的幫助是自動將異常重定向到路由為/e
測試開發專題:spring-boot統一異常捕獲
# java異常介紹 異常時相對於return的一種退出機制,可以由系統觸發,也可由程式通過throw語句觸發,異常可以通過try/catch語句進行捕獲並處理,如果沒有捕獲,則會導致程式退出並輸出異常棧資訊,異常有不同的型別,所有異常類都有一個共同的父類Throwable,下面我們先從Throwable開
spring boot 架構問題 時間處理 (對映,時區問題)
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.time-zone=GMT+8 4.12日 http://blog.csdn.net/buzaiguihun/article/details/
spring boot 統一返回資料及全域性異常處理
記錄關於spring boot 統一返回資料及全域性異常處理的操作實現。 一、統一返回資料 1、定義一個超類:BaseResponseVo @Data @NoArgsConstructor public class BaseResponseVo{ protected Integer r
spring boot 系統異常統一處理
1.系統異常捕獲 @ControllerAdvice(annotations = {RestController.class}) public class GlobalExceptionHandler { private Logger logger = LoggerFactory.get
Spring Boot 統一返回資料結構以及全域性異常處理
前言 看了廖師兄的視訊後,結合自己以前的程式設計經驗總結下 : 在 web 開發過程中, 後端要統一返回的資料結構,便於前端處理。例如每個請求,我們都需要知道 : code : 伺服器返回的狀態碼(主要給程式設計師看)。例如 : 200 : 請求成功。
spring @ControllerAdvice統一異常處理 Ajax和普通請求
import com.alibaba.fastjson.JSON; import com.zh.entity.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; im
詳解Spring MVC/Boot 統一異常處理最佳實踐
開發十年,就只剩下這套架構體系了! >>>
使用Spring MVC統一異常處理實戰
tro 處理機制 tor attr 運行 target icon message 404錯誤 原文地址:http://cgs1999.iteye.com/blog/1547197 1 描述 在J2EE項目的開發中,不管是對底層的數據庫操作過程,還是業務層的處理過程,還是控
Spring Boot Environment的初始化和預處理
bst div 方法 ams jdb ram rep ntp table Spring Boot Environment的初始化和預處理實在啟動時完成的, 即SpringApplication的run方法中。 ConfigurableEnvironment environ
Spring Boot 全域性異常處理 與 Hibernate Validator校驗框架整合
Hibernate Validator校驗框架的使用 Spring boot已經集成了hibernate-validator,不需要引入maven,其他框架也可以自己引入: <dependency> <groupId>org.h
Spring MVC 統一異常處理的兩種方式
沒有廢話,直接來。 方式一 通過@ControllerAdvice 和 @ExceptionHandler 方法。 @ControllerAdvice 這個註解,可以將對於控制器的全域性配置放到註解了@ControllerAdvice的類上,它結合了 @Component 所以可
利用Spring進行統一異常處理的兩種方式
package com.jay.platform.exception.handler; import java.io.IOException; import java.net.ConnectException; import java.net.SocketTimeoutException; import
spring boot 全域性異常處理及自定義異常類
全域性異常處理:定義一個處理類,使用@ControllerAdvice註解。@ControllerAdvice註解:控制器增強,一個被@Component註冊的元件。配合@ExceptionHandler來增強所有的@requestMapping方法。例如:@Exceptio
Spring MVC 統一異常處理總結
在一個Spring MVC專案中,使用統一異常處理,可以使維護程式碼變得容易。下面總結一下常用的3種方法。 實現HandlerExceptionResolver介面 實現HandlerExcep
9.玩轉Spring Boot 全域性異常處理@ControllerAdvice
玩轉Spring Boot 全域性異常處理@ControllerAdvice 在開發中出現異常後,可能需要一個統一處理的地方,來處理程式出現的異常,針對不同的異常做不同的處理,這裡我們通過@ExceptionHandler註解來實現。在WEB開發中,比如頁面出
Spring Boot 全域性異常處理
當我們在開發一個專案時,往往需要對異常進行捕獲處理,以提供友好的資訊展示給使用者。但隨著業務