溫馨提示×

溫馨提示×

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

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

Spring Boot 統(tǒng)一異常怎么處理和剖析

發(fā)布時間:2021-10-20 10:19:01 來源:億速云 閱讀:159 作者:柒染 欄目:大數(shù)據(jù)

今天就跟大家聊聊有關Spring Boot 統(tǒng)一異常怎么處理和剖析,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

話說異常

「欲渡黃河冰塞川,將登太行雪滿天」,無論生活還是計算機世界難免發(fā)生異常,上一篇文章RESTful API 返回統(tǒng)一JSON數(shù)據(jù)格式 說明了統(tǒng)一返回的處理,這是請求一切正常的情形;這篇文章將說明如何統(tǒng)一處理異常,以及其背后的實現(xiàn)原理,老套路,先實現(xiàn),后說明原理,有了上一篇文章的鋪底,相信,理解這篇文章就駕輕就熟了

Spring Boot 統(tǒng)一異常怎么處理和剖析

實現(xiàn)

新建業(yè)務異常

新建 BusinessException.class 類表示業(yè)務異常,注意這是一個 Runtime 異常

@Data
@AllArgsConstructor
public final class BusinessException extends RuntimeException {

	private String errorCode;

	private String errorMsg;
	
}

添加統(tǒng)一異常處理靜態(tài)方法

在 CommonResult 類中添加靜態(tài)方法 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 注解,將統(tǒng)一異常添加到配置中:

@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, 我們都會返回給前端一個統(tǒng)一數(shù)據(jù)格式

測試

將 UserController 中的方法進行改造,直接拋出異常:

@GetMapping("/{id}")
public UserVo getUserById(@PathVariable Long id){
    throw new BusinessException("1001", "根據(jù)ID查詢用戶異常");
}

瀏覽器中輸入: http://localhost:8080/users/1

Spring Boot 統(tǒng)一異常怎么處理和剖析

在 Service 中拋出異常:

@Service
public class UserServiceImpl implements UserService {

	/**
	 * 根據(jù)用戶ID查詢用戶
	 *
	 * @param id
	 * @return
	 */
	@Override
	public UserVo getUserById(Long id) {
		throw new BusinessException("1001", "根據(jù)ID查詢用戶異常");
	}
}

運行是得到同樣的結果,所以我們盡可能的拋出異常吧 (作為一個程序猿這種心理很可拍)

解剖實現(xiàn)過程

解剖這個過程是相當糾結的,為了更好的說(yin)明(wei)問(wo)題(lan),我要說重中之重了,真心希望看該文章的童鞋自己去案發(fā)現(xiàn)場發(fā)現(xiàn)線索 還是在 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 實現(xiàn)了 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 注解標記的方法并放到集合當中,用于后續(xù)全局異常捕獲的匹配

/**
 * 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 {
            ...
            // 當請求發(fā)生異常,該方法會通過 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 方法,這里只要展示調用棧你就會眼前一亮了,又是為了返回統(tǒng)一格式數(shù)據(jù):

Spring Boot 統(tǒng)一異常怎么處理和剖析

總結

上一篇文章的返回統(tǒng)一數(shù)據(jù)格式是基礎,當異常情況發(fā)生時,只不過需要將異常信息提取出來。本文主要為了說明問題,剖析原理,好多地方設計方式是不可取,比如我們最好將異常封裝在一個 Enum 類,通過 enum 對象拋出異常等,如果你用到這些,去完善你的設計方案吧

回復 「demo」,打開鏈接,查看文件夾 「unifiedreturn」下內(nèi)容,獲取完整代碼,更好閱讀體驗: https://fraseryu.github.io/2019/08/09/ru-he-tong-yi-chu-li-yi-chang-bing-fan-hui-tong-yi-ge-shi/

附加說明

之前看到的一本書對異常的分類讓我印象深刻,在此摘錄一小段分享給大家: Spring Boot 統(tǒng)一異常怎么處理和剖析

結合出國旅行的例子說明異常分類:

  • 機場地震,屬于不可抗力,對應異常分類中的 Error,在制訂出行計劃時,根本不需要把這個部分的異??紤]進去

  • 堵車屬于 checked 異常,應對這種異常,我們可以提前出發(fā),或者改簽機票。而飛機延誤異常,雖然也需要 check,但我們無能為力,只能持續(xù)關注航班動態(tài)

  • 沒有帶護照,明顯屬于可提前預測的異常,只要出發(fā)前檢查即可避免;去機場路上車子拋錨,這個異常是突發(fā)的,雖然難以預料,但是必須處理,屬于需要捕捉的異常,可以通過更換交通工具;應對檢票機器故障屬于 可透出異常,交由航空公司處理,我們無須關心

靈魂追問

  1. 這兩篇文章,你學到了哪些設計模式?

  2. 你能熟練的使用反射嗎?當看源碼是會看到很多反射的應用

  3. 你了解 Spring CGLIB 嗎?它的工作原理是什么?

提高效率工具

Spring Boot 統(tǒng)一異常怎么處理和剖析

JSON-Viewer

JSON-Viewer 是 Chrome 瀏覽器的插件,用于快速解析及格式化 json 內(nèi)容,在 Chrome omnibox(多功能輸入框)輸入json-viewer + TAB ,將 json 內(nèi)容拷貝進去,然后輸入回車鍵,將看到結構清晰的 json 數(shù)據(jù),同時可以自定義主題

Spring Boot 統(tǒng)一異常怎么處理和剖析

另外,前端人員打開開發(fā)者工具,雙擊請求鏈接,會自動將 response 中的 json 數(shù)據(jù)解析出來,非常方便

看完上述內(nèi)容,你們對Spring Boot 統(tǒng)一異常怎么處理和剖析有進一步的了解嗎?如果還想了解更多知識或者相關內(nèi)容,請關注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細節(jié)

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

AI