您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關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 是 Chrome 瀏覽器的插件,用于快速解析及格式化 json 內容,在 Chrome omnibox(多功能輸入框)輸入json-viewer + TAB
,將 json 內容拷貝進去,然后輸入回車鍵,將看到結構清晰的 json 數據,同時可以自定義主題
看完上述內容,你們對Spring Boot 統一異常怎么處理和剖析有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。