spring boot / cloud (十二) 異常統一處理進階

spring boot / cloud (十二) 異常統一處理進階

前言

spring boot / cloud (二) 規範響應格式以及統一異常處理這篇博客中已經提到了使用@ExceptionHandler來處理各類類型的異常,這種方式也是互聯網上普遍的方式html

今天這篇博客,將介紹一種spring boot官方文檔上的統一處理異常的方式.你們能夠在spring boot 官方文檔查看介紹java

在開始介紹新的方法以前 , 咱們先來分析一下 , 之前的作法有那些地方是須要優化的git

場景分析

一般咱們須要作統一異常處理的需求,大概都是要規範異常輸出,以及處理,通同一套抽象出來的邏輯來處理全部異常.spring

可是在當前流行RestFul風格接口的環境下,對異常的輸出還作了額外的一個要求,就是針對不一樣的錯誤須要輸出對應的http狀態.app

在前面的實現中,咱們大能夠指定一個處理Exception的@ExceptionHandler,這樣全部異常都能囊括了,可是卻沒法很好的將http狀態區分開來.框架

若是要實現不一樣的異常輸出不一樣的http狀態,在原來的作法裏就要將每一個異常都窮舉出來,而後作不一樣的設定.ide

顯然,咱們是不但願這樣作的,顯得太不聰明,不過還好,spring已經幫咱們把這一步已經作掉了,咱們只需處理本身關心的異常便可spring-boot

@ExceptionHandler(value = 要攔截的異常.class)
@ResponseStatus(響應狀態)
@ResponseBody
public RestResponse<String> exception(要攔截的異常 exception) {
  return new RestResponse<>(ErrorCode.ERROR, buildError(exception));
}

@ExceptionHandler(value = Exception.class)
@ResponseStatus(500)
@ResponseBody
public RestResponse<String> exception(Exception exception) {
  return new RestResponse<>(ErrorCode.ERROR, buildError(exception));
}

源碼解讀

官方文檔中指出,你須要實現一個類,使用@ControllerAdvice標註,而後繼承至ResponseEntityExceptionHandler類.優化

這個ResponseEntityExceptionHandler類是一個抽象類,以下是它的核心方法ui

@ExceptionHandler({
        NoSuchRequestHandlingMethodException.class,
        HttpRequestMethodNotSupportedException.class,
        .....省略
    })
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
        HttpHeaders headers = new HttpHeaders();
        if (ex instanceof NoSuchRequestHandlingMethodException) {
            HttpStatus status = HttpStatus.NOT_FOUND;
            return handleNoSuchRequestHandlingMethod(
            (NoSuchRequestHandlingMethodException) ex, 
            headers, status, request);
        }
        else if (ex instanceof HttpRequestMethodNotSupportedException) {
            HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
            return handleHttpRequestMethodNotSupported(
            (HttpRequestMethodNotSupportedException) ex, headers, status, request);
        } else if (..............){
            .....省略
        } else {
            .....省略
            return handleExceptionInternal(ex, null, headers, status, request);
        }
}

在以上的代碼片斷中,咱們能夠看到handleException方法已經把常見的異常都攔截掉了,而且作出了適當的處理,而且在最後,else分支裏,調用了handleExceptionInternal方法,

這個方法就是處理沒有被攔截到的異常,而後這也是咱們要進行擴展的地方

實現

實現ExceptionHandle類,繼承至ResponseEntityExceptionHandler,而且註解@ControllerAdvice

@ControllerAdvice
@Slf4j
public class ExceptionHandle extends ResponseEntityExceptionHandler {
    .....
}

實現exception方法,使用@ExceptionHandler攔截Exception,那麼在這裏,全部的異常都會進入這個方法進行處理.

而後調用父類的handleException方法(上面提到的),讓spring默認的異常處理先處理一遍,若是當前的異常恰巧是被spring攔截的,那麼就用spring的默認實現處理,就無需在寫額外的代碼了,http狀態碼也一併的會設置好.

最後在調用咱們即將要重寫的方法handleExceptionInternal,來處理自定義異常以及規範異常輸出

@ExceptionHandler(value = Exception.class)
public ResponseEntity<Object> exception(Exception ex, WebRequest request) {
    ResponseEntity<Object> objectResponseEntity = this.handleException(ex, request);
    return this.handleExceptionInternal(ex, null, objectResponseEntity.getHeaders(), objectResponseEntity.getStatusCode(), request);
}

重寫handleExceptionInternal方法,

在這個方法裏面,能夠向以下實現同樣,去處理項目中自定義的異常,將其規範爲想要的輸出格式,

最後再調用父類的handleExceptionInternal方法,將控制權交還給spring,

這樣就完成了整個異常處理的流程

@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
    HttpStatus localHttpStatus = status;
    ErrorResult errorResult = buildError(applicationConfig, ex);
    if (ex instanceof PermissionException) { //權限異常
        localHttpStatus = HttpStatus.FORBIDDEN;
    } else if (ex instanceof AuthException) { //認證異常
        localHttpStatus = HttpStatus.UNAUTHORIZED;
    } else if (ex instanceof ParameterValidException) { //參數校驗異常
        localHttpStatus = HttpStatus.BAD_REQUEST;
    } else if (ex instanceof RestClientResponseException) { //rest請求異常
        try {
            RestClientResponseException restClientResponseException = (RestClientResponseException) ex;
            String data = restClientResponseException.getResponseBodyAsString();
            if (StringUtils.isNotBlank(data)) {
                RestResponse<String> child = objectMapper.readValue(data, objectMapper.getTypeFactory().constructParametricType(RestResponse.class, String.class));
                errorResult.setChild(child);
            }
        } catch (IOException e) {
            throw new SystemRuntimeException(e);
        }
    }
    log.error(ex.getClass().getName(), ex);
    return super.handleExceptionInternal(ex, new RestResponse<>(localHttpStatus, errorResult), headers, localHttpStatus, request);
}

結束

在上面咱們優化了統一異常處理的代碼,作到了只關心繫統自定義異常的處理,框架和容器的異常處理,交由spring處理,簡化了代碼,避免了重複造輪子,同時代碼也更加健壯了.

在下一篇文章中,我會介紹另一個更合裏的處理404錯誤的方式,敬請期待

代碼倉庫 (博客配套代碼)


想得到最快更新,請關注公衆號

想得到最快更新,請關注公衆號

相關文章
相關標籤/搜索