1. 程式人生 > >Spring Boot 全域性異常處理

Spring Boot 全域性異常處理

    當我們在開發一個專案時,往往需要對異常進行捕獲處理,以提供友好的資訊展示給使用者。但隨著業務的增長,專案越來越複雜,需要捕獲異常的地方就會越來越多,如果每個地方都進行try catch,那程式碼將會變得非常冗餘且不好維護。

       我們知道Spring Boot預設情況下會對映到 /error 進行異常處理,但是提示並不十分友好。那有沒有一種統一的處理機制?幸好從Spring 3.2以後新增了@ControllerAdvice 註解,使用AOP對Controller控制器進行增強(前置增強、後置增強、環繞增強),那麼我們就可以對控制器的方法進行呼叫前(前置增強)和呼叫後(後置增強)的處理。

       Spring還提供了@ExceptionHandler異常增強註解,一般需要配合@RequestBody註解使用。程式如果在執行控制器方法前或執行時丟擲異常,會被@ExceptionHandler註解了的方法處理。如果全部異常處理返回json格式,那麼可以使用@RestControllerAdvice 代替@ControllerAdvice,這樣在方法上就可以不需要新增@ResponseBody。

自定義異常類

定義一個AgException作為全域性的自定義異常。繼承RuntimeException(執行時異常)對程式碼無可侵入性,不需要方法中強制捕獲或者丟擲。

@Getter
@Slf4j
public class AgException extends RuntimeException {

    /**
     * 響應狀態碼列舉
     */
    private StatusResultEnum statusResult;

    private Object[] args;

    /**
     * 構造指定異常程式碼與訊息引數的業務異常。
     *
     * @param statusResult 異常程式碼
     * @param args 訊息引數,該引數將用於格式化異常程式碼中的訊息字串
     */
    public AgException(StatusResultEnum statusResult, Object... args) {
        this(statusResult, null, args);
    }

    /**
     * 構造指定異常程式碼、異常原因與訊息引數的業務異常。
     *
     * @param statusResult 異常程式碼
     * @param cause 異常訊息
     * @param args 訊息引數,該引數將用於格式化異常程式碼中的訊息字串
     */
    public AgException(StatusResultEnum statusResult, Throwable cause, Object... args) {
        super(statusResult.getCodeMsg(args), cause);
        log.error("系統異常:{} ", cause.getMessage(), cause);
        this.args = args;
        this.statusResult = statusResult;
    }

}

新增自定義資訊列舉類

public enum StatusResultEnum {

    SUCCESS("2000", "success", "請求成功"),

    /**
     * 可預知異常
     */
    NOT_LOGIN_IN("4001", "未登入", "未登入"),
    /**
     * 不可預知異常,但有明確錯誤碼
     */
    UN_AUTHORIZED("4002", "許可權不足", "許可權不足"),
    /**
     * 不可預知異常,預設錯誤
     */
    INTERNAL_SERVER_ERROR("5000", "%1s", "內部伺服器錯誤,請聯絡客服人員。");

    /**
     * 響應返回碼
     */
    @Getter
    private String code;
    /**
     * 響應描述,面向開發者
     */
    @Setter
    private String codeMsg;
    /**
     * 響應描述,面向使用者
     */
    @Setter
    private String statusMsg;

    StatusResultEnum(String code, String codeMsg, String statusMsg) {
        this.code = code;
        this.codeMsg = codeMsg;
        this.statusMsg = statusMsg;
    }

    /**
     * 根據指定的佔位符引數格式化異常訊息。
     *
     * @param args 佔位符引數
     * @return 格式化後的異常訊息
     */
    public String getCodeMsg(Object... args) {
        return ErrorCodeUtils.formatMessage(codeMsg, args);
    }

    /**
     * 根據指定的佔位符引數格式化異常訊息。
     *
     * @param args 佔位符引數
     * @return 格式化後的異常訊息
     */
    public String getStatusMsg(Object... args) {
        return ErrorCodeUtils.formatMessage(statusMsg, args);
    }

}

全域性異常處理類

  • 對異常進行歸類:可預知異常(即自定義異常AgException);不可預知異常,但需要明確定義錯誤碼;不可預知異常,不需特殊處理錯誤碼。
  • 使用Spring MVC控制器增強,捕獲全域性異常。
  • 捕獲AgException業務異常,取出錯誤碼和資訊構造響應。
  • 使用一個執行緒安全、並且不可更改的map儲存不可預知異常自定義的錯誤資訊。
  • 捕獲AgException以外的異常(Exception),判斷map是否定義了該異常錯誤資訊,若有定義取出錯誤資訊構造響應,否則返回預設錯誤。
@RestControllerAdvice
@Slf4j
public class AgExceptionHandler {

    /**
     * 執行緒安全
     */
    private static final ImmutableMap<Class<? extends Throwable>, StatusResultEnum> EXCEPTIONS;

    static {
        final ImmutableMap.Builder<Class<? extends Throwable>, StatusResultEnum> builder = ImmutableMap.builder();

        builder.put(LockedAccountException.class, StatusResultEnum.IDENTITY_AUTH_FAIL);
        builder.put(UnknownAccountException.class, StatusResultEnum.IDENTITY_AUTH_FAIL);
        builder.put(IncorrectCredentialsException.class, StatusResultEnum.IDENTITY_AUTH_FAIL);
        builder.put(DisabledAccountException.class, StatusResultEnum.IDENTITY_AUTH_FAIL);

        builder.put(UnauthorizedException.class, StatusResultEnum.UN_AUTHORIZED);
        builder.put(MissingServletRequestParameterException.class, StatusResultEnum.REQUIRE_ARGUMENT);

        // 其他未被發現的異常
        builder.put(Exception.class, StatusResultEnum.INTERNAL_SERVER_ERROR);

        EXCEPTIONS = builder.build();
    }

    @ExceptionHandler(AgException.class)
    public BaseResponse handleAgException(Throwable e) {
        AgException agException = (AgException) e;
        return new ResultResponse(agException.getStatusResult(), agException.getArgs());
    }

    @ExceptionHandler(Exception.class)
    public BaseResponse handleException(Exception e) {
        log.error("系統異常:{} ", e.getMessage(), e);
        StatusResultEnum statusResultEnum = EXCEPTIONS.get(e.getClass());
        return new ResultResponse(statusResultEnum, e.getMessage());
    }

}

 

 

總結

異常丟擲的順序為Dao—Service—Controller—AgExceptionHandler,SpringMVC增強的即是在Controller層進行攔截,實現全域性異常統捕獲,異常在AgExceptionHandler 統一處理後,就無需再程式碼中單獨對每個服務進行try catch,此種實現方式程式碼不僅重用性高,而