SpringBoot之全域性異常處理

chetwhy發表於2019-04-21

異常處理問題分析

異常如何處理

問題引入

  • 針對程式碼中的異常,常規有兩種處理方式,一種throws直接丟擲,另一種try..catch捕獲。

  • 在java專案中,有可能存在人為邏輯的異常,也可能為取得異常的詳情,或是保證程式在異常時繼續向下執行,會採用第二種處理方式。

  • 但是,程式碼中每一處異常都來捕獲,會使程式碼什麼冗餘且不利於維護。

解決思路

  • 定義一個全域性異常處理類,返回統一規範的異常資訊;

  • 處理邏輯是,先判定是否會出現異常,再執行後續具體的業務。

業務舉例

本文主要為了實現全域性異常處理的邏輯,只舉簡單業務

某公司部門需增加員工,處理流程:1先根據員工編號查詢員工物件,2判斷員工物件是否有資訊,即是否不為空,3若有資訊,則說明已存在,無需再新增,若不是,則直接新增。

程式碼如下:

public class MyService {
    // 注入dao層
    @Autowired
    EmployeeecMapper employeeecMapper;

    /**
     * 新增員工資訊
     * @param employee 員工物件
     * @return 影響的行數
     */
    public int add(Employee employee) {
        // 根據id查詢員工物件
        Employeeec emp = employeeecMapper.selectByPrimaryKey(employee.getId());
        // 判斷是否已有該員工
        if (emp != null){
            // 已有,丟擲異常,異常資訊為已有該員工
            throw new RuntimeException("異常程式碼:1201,錯誤資訊:該員工已存在");
        }
        // 沒有,插入該員工
        return employeeecMapper.insert(emp);
    }
}
複製程式碼

異常處理流程

業務中存在執行時異常和業務邏輯異常,前者不執行時很難察覺,後者在遍及業務時就可以定義出來,因此異常分為不可預知異常和可知異常。流程如下:

  1. 自定義全域性異常類,使用@ControllerAdvice,控制器增強
  2. 自定義錯誤程式碼及錯誤資訊,兩種異常最終會採用統一的資訊格式來表示,錯誤程式碼+錯誤資訊。
  3. 對於可預知的異常由程式設計師在程式碼中主動丟擲,由SpringMVC統一捕獲。
  4. 不可預知異常通常是由於系統出現bug、或一些外界因素(如網路波動、伺服器當機等),異常型別為RuntimeException型別(執行時異常)。

可知異常

定義異常資訊類,變數為錯誤程式碼和錯誤資訊,捕獲自定義異常時,直接將該物件返回

不可知異常

定義一個map,將常見的異常存入其中,並定義錯誤程式碼。對於其他不常見的異常,即map中沒有的,同一一個異常物件返回即可。

異常處理程式碼流程

可知異常

1、定義列印異常資訊與返回結果的介面

public interface ResultCode {
    // 操作是否成功
    boolean success();

    // 操作結果程式碼
    long code();

    // 提示資訊
    String message();
}
複製程式碼
public interface Response {
    public static final boolean SUCCESS = true;
    public static final int SUCCESS_CODE = 10000;
}
複製程式碼

2、定義列印異常資訊的列舉類和返回結果類

@ToString
public enum  CommonCode implements ResultCode {
    NO_PAGE(false,404,"沒有資訊"),
    FAIL(false,500,"操作失敗!"),
    SUCCESS(true,200,"操作成功!");

    // 結果資訊
    boolean success;
    long code;
    String message;
    
    // 帶參構造
    CommonCode(boolean success, long code, String message) {
        this.success = success;
        this.code = code;
        this.message = message;
    }

    @Override
    public boolean success() {
        return true;
    }

    @Override
    public long code() {
        return code;
    }

    @Override
    public String message() {
        return message;
    }
}
複製程式碼
@Data
@ToString
public class ResponseResult implements Response {

    boolean success = SUCCESS;

    long code = SUCCESS_CODE;

    String message;

    public ResponseResult(ResultCode resultCode){
        this.success = resultCode.success();
        this.code = resultCode.code();
        this.message = resultCode.message();
    }
}
複製程式碼

3、定義錯誤異常類

public class CustomException extends RuntimeException{

    @Autowired
    ResultCode resultCode;
    
    // 帶參構造
    public CustomException(ResultCode resultCode){
        this.resultCode = resultCode;
    }
    
    // getter
    public ResultCode getResultCode(){
        return resultCode;
    }
}
複製程式碼

4、定義異常丟擲類

public class ExceptionCast {

    // 靜態方法
    public static void cast(ResultCode resultCode){
        throw new CustomException(resultCode);
    }
}
複製程式碼

5、定義異常捕獲類,使用ControllerAdvice控制器增強的註解,並在捕獲CustomException異常的方法上加ExceptionHandler註解,即可捕獲該類的所有異常,返回json資料。

@ControllerAdvice 
public class ExceptionCatch {

    /**
     * 捕獲CustomException類異常
     * @param customException
     * @return 結果資訊,json資料
     */
    @ExceptionHandler(CustomException.class)
    @ResponseBody
    public ResponseResult customException(CustomException customException){
        ResultCode resultCode = customException.getResultCode();
        return new ResponseResult(resultCode);
    }
}
複製程式碼

6、在業務中丟擲異常

public class MyService {

    @Autowired
    EmployeeecMapper employeeecMapper;

    public int add(Employee employee) {
        Employeeec emp = employeeecMapper.selectByPrimaryKey(employee.getId());
        if (emp != null){
            ExceptionCast.cast(CommonCode.FAIL);
        }
        return employeeecMapper.insert(emp);
    }
}
複製程式碼

不可知異常處理

1、類似可知異常,先在CommonCode類中新增錯誤程式碼,如

UNAUTHORISE(false,510,"沒有許可權"),
複製程式碼

2、在異常捕獲類中新增不可知異常的捕獲方法。該方法中,定義一個只讀的map儲存異常型別的錯誤程式碼的對映,map中沒有的元素,統一用錯誤程式碼999來定義。

UNKNOWNERROR(false,999,"未知異常"),
複製程式碼
@ControllerAdvice
public class ExceptionCatch {

    // 定義map,存貯常見錯誤資訊。該類map不可修改
    private static ImmutableMap<Class<? extends Throwable>,ResultCode> EXCEPTIONS;
    // 構建ImmutableMap
    protected static ImmutableMap.Builder<Class<? extends Throwable>,ResultCode> builder = ImmutableMap.builder();

    @ExceptionHandler(CustomException.class)
    @ResponseBody
    public ResponseResult customException(CustomException customException){
        ResultCode resultCode = customException.getResultCode();
        return new ResponseResult(resultCode);
    }

    /**
     * 捕獲非自定義類異常
     * @param exception
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseResult exception(Exception exception){
        // 記錄日誌
        LOGGER.error("catch exception ==> ",exception.getMessage());
        if (EXCEPTIONS == null){
            EXCEPTIONS = builder.build();
        }
        ResultCode resultCode = EXCEPTIONS.get(exception.getClass());
        if (resultCode != null){
            return new ResponseResult(resultCode);
        }else {
            return new ResponseResult(CommonCode.UNKNOWNERROR);
        }
    }

    static {
        builder.put(HttpMessageNotReadableException.class, CommonCode.INVALID_PARAM);
    }
}
複製程式碼

完成~~

相關文章