SpringBoot全局異常處理

一.SpringBoot全局異常處理

1.1 全局異常處理的好處

  1. 后端開發(fā)人員職責(zé)單一蝴猪,只需要將異常捕獲并轉(zhuǎn)換為自定義異常一直對外拋出。不需要去想頁面跳轉(zhuǎn)404膊爪,以及異常響應(yīng)的數(shù)據(jù)結(jié)構(gòu)的設(shè)計自阱。
  2. 面向前端人員友好,后端返回給前端的數(shù)據(jù)應(yīng)該有統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)米酬,統(tǒng)一的規(guī)范沛豌。而在此過程中不需要后端開發(fā)人員做更多的工作,交給全局異常處理器去處理“異吃叨睿”到“響應(yīng)數(shù)據(jù)結(jié)構(gòu)”的轉(zhuǎn)換加派。
  3. 面向用戶友好叫确,用戶能夠清楚的知道異常產(chǎn)生的原因。這就要求自定義異常芍锦,全局統(tǒng)一處理竹勉,接口請求響應(yīng)統(tǒng)一的異常數(shù)據(jù)結(jié)構(gòu),頁面模板請求統(tǒng)一跳轉(zhuǎn)到404頁面娄琉。
  4. 面向運維友好次乓,將異常信息合理規(guī)范的持久化,以日志的形式存儲起來孽水,以便查詢票腰。

1.2開發(fā)規(guī)范

  1. Controller、Service匈棘、Repository層攔截異常轉(zhuǎn)換為自定義異常丧慈,不允許將異常私自截留。必須對外拋出主卫。
  2. 統(tǒng)一數(shù)據(jù)響應(yīng)代碼逃默,默認使用http狀態(tài)碼(200、400簇搅、500等少數(shù)幾個)完域,特殊場景可自定義。
  3. 自定義異常里面有message屬性瘩将,用對用戶友好的語言描述異常的發(fā)生情況吟税,并賦值給message。
  4. 不允許對父類Exception統(tǒng)一catch姿现,要分小類catch肠仪,這樣能夠清楚地將異常轉(zhuǎn)換為自定義異常傳遞給前端。

二.自定義異常和相關(guān)數(shù)據(jù)結(jié)構(gòu)

2.1 自定義異常

1.枚舉異常的類型

public enum CustomExceptionType {

    OK(HttpStatus.OK.value(),null),
    BAD_REQUEST(HttpStatus.BAD_REQUEST.value(),"您輸入的數(shù)據(jù)錯誤或您沒有權(quán)限訪問資源备典!"),
    INTERNAL_SERVER_ERROR (HttpStatus.INTERNAL_SERVER_ERROR.value(),"系統(tǒng)出現(xiàn)異常异旧,請您稍后再試或聯(lián)系管理員!"),
    OTHER_ERROR(999,"系統(tǒng)出現(xiàn)未知異常提佣,請聯(lián)系管理員吮蛹!");

    CustomExceptionType(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    private String desc;//異常類型中文描述

    private int code; //code

    public String getDesc() {
        return desc;
    }

    public int getCode() {
        return code;
    }
}

2.自定義異常

public class CustomException extends RuntimeException {
    //異常錯誤編碼
    private int code ;
    //異常信息
    private String message;

    private CustomException(){}

    public CustomException(CustomExceptionType exceptionTypeEnum) {
        this.code = exceptionTypeEnum.getCode();
        this.message = exceptionTypeEnum.getDesc();
    }

    public CustomException(CustomExceptionType exceptionTypeEnum, String message) {
        this.code = exceptionTypeEnum.getCode();
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }
}
  • 自定義異常有兩個核心內(nèi)容,一個是code拌屏。使用CustomExceptionType 來限定范圍潮针。
  • 另外一個是message,這個message信息是要最后返回給前端的倚喂,所以需要用友好的提示來表達異常發(fā)生的原因或內(nèi)容

2.2 請求接口統(tǒng)一響應(yīng)數(shù)據(jù)結(jié)構(gòu)

對于1.2節(jié)中的配置文件每篷,可以通過@ConfigurationProperties注解獲取配置信息并對的Java類屬性進行賦值。

@Data
public class ApiResponse {

    private boolean isok;  //請求是否處理成功
    private int code; //請求響應(yīng)狀態(tài)碼(200、400雳攘、500)
    private String message;  //請求結(jié)果描述信息
    private Object data; //請求結(jié)果數(shù)據(jù)(通常用于查詢操作)

    private ApiResponse(){}

    public static ApiResponse error(CustomException e) {
        ApiResponse resultBean = new ApiResponse();
        resultBean.setIsok(false);
        resultBean.setCode(e.getCode());
        resultBean.setMessage(e.getMessage());
        return resultBean;
    }

    public static ApiResponse error(CustomExceptionType customExceptionType,
                                     String errorMessage) {
        ApiResponse resultBean = new ApiResponse();
        resultBean.setIsok(false);
        resultBean.setCode(customExceptionType.getCode());
        resultBean.setMessage(errorMessage);
        return resultBean;
    }

    public static ApiResponse success(){
        ApiResponse apiResponse = new ApiResponse();
        apiResponse.setIsok(true);
        apiResponse.setCode(200);
        apiResponse.setMessage("請求響應(yīng)成功!");
        return apiResponse;
    }

    public static ApiResponse success(Object obj){
        ApiResponse apiResponse = new ApiResponse();
        apiResponse.setIsok(true);
        apiResponse.setCode(200);
        apiResponse.setMessage("請求響應(yīng)成功!");
        apiResponse.setData(obj);
        return apiResponse;
    }

    public static ApiResponse success(Object obj,String message){
        ApiResponse apiResponse = new ApiResponse();
        apiResponse.setIsok(true);
        apiResponse.setCode(200);
        apiResponse.setMessage(message);
        apiResponse.setData(obj);
        return apiResponse;
    }
}

為了解決不同的開發(fā)人員使用不同的結(jié)構(gòu)來響應(yīng)給前端带兜,導(dǎo)致規(guī)范不統(tǒng)一枫笛,開發(fā)混亂的問題吨灭。我們使用如下代碼定義統(tǒng)一數(shù)據(jù)響應(yīng)結(jié)構(gòu)

  • isok表示該請求是否處理成功(即是否發(fā)生異常)。true表示請求處理成功刑巧,false表示處理失敗喧兄。
  • code對響應(yīng)結(jié)果進一步細化,200表示請求成功啊楚,400表示用戶操作導(dǎo)致的異常吠冤,500表示系統(tǒng)異常,999表示其他異常恭理。與CustomExceptionType枚舉一致拯辙。
  • message:友好的提示信息,或者請求結(jié)果提示信息颜价。如果請求成功這個信息通常沒什么用涯保,如果請求失敗,該信息需要展示給用戶周伦。
  • data:通常用于查詢數(shù)據(jù)請求夕春,成功之后將查詢數(shù)據(jù)響應(yīng)給前端。

三.通用全局異常處理邏輯

程序員的異常處理邏輯要十分的單一:無論在Controller層专挪、Service層還是什么其他位置及志,程序員只負責(zé)一件事:那就是捕獲異常,并將異常轉(zhuǎn)換為自定義異常寨腔。使用用戶友好的信息去填充CustomException的message,并將CustomException拋出去速侈。###3.1 對綁定的屬性值進行校驗

3.1 全局異常處理器

通過團隊內(nèi)的編碼規(guī)范的要求,我們已經(jīng)知道了:不允許程序員截留處理Exception迫卢,必須把異常轉(zhuǎn)換為自定義異常CustomException全都拋出去倚搬。那么程序員把異常跑出去之后由誰來處理?那就是ControllerAdvice靖避。
ControllerAdvice注解的作用就是監(jiān)聽所有的Controller潭枣,一旦Controller拋出CustomException,就會在@ExceptionHandler(CustomException.class)注解的方法里面對該異常進行處理幻捏。處理方法很簡單就是使用ApiResponse.error(e)包裝為通用的接口數(shù)據(jù)結(jié)構(gòu)返回給前端盆犁。

@ControllerAdvice
public class WebExceptionHandler {

    //處理程序員主動轉(zhuǎn)換的自定義異常
    @ExceptionHandler(CustomException.class)
    @ResponseBody
    public ApiResponse customerException(CustomException e) {
        if(e.getCode() == CustomExceptionType.SYSTEM_ERROR.getCode()){
                 //400異常不需要持久化,將異常信息以友好的方式告知用戶就可以
                //TODO 將500異常信息持久化處理篡九,方便運維人員處理
        }
        return ApiResponse.error(e);
    }

    //處理程序員在程序中未能捕獲(遺漏的)異常
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ApiResponse exception(Exception e) {
        return ApiResponse.error(new CustomException(CustomExceptionType.OTHER_ERROR));
    }

    //處理異常校驗失敗
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public ApiResponse handleBindException(MethodArgumentNotValidException ex) {
        FieldError fieldError = ex.getBindingResult().getFieldError();
        return ApiResponse.error(new CustomException(CustomExceptionType.BAD_REQUEST,
                fieldError.getDefaultMessage()));
    }

    //處理異常校驗失敗
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public ApiResponse handleBindException(BindException ex) {
        FieldError fieldError = ex.getBindingResult().getFieldError();
        return ApiResponse.error(new CustomException(CustomExceptionType.BAD_REQUEST,
                fieldError.getDefaultMessage()));
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseBody
    public ApiResponse handleIllegalArgumentException(IllegalArgumentException e) {
        return ApiResponse.error(
                new CustomException(CustomExceptionType.BAD_REQUEST, e.getMessage())
        );
    }

    @ExceptionHandler(ModelViewException.class)
    public ModelAndView viewExceptionHandler(HttpServletRequest req, ModelViewException e) {
        ModelAndView modelAndView = new ModelAndView();

        //將異常信息設(shè)置如modelAndView
        modelAndView.addObject("exception", e);
        modelAndView.addObject("url", req.getRequestURL());
        modelAndView.setViewName("error");

        //返回ModelAndView
        return modelAndView;
    }
}

3.2 讓業(yè)務(wù)狀態(tài)與HTTP協(xié)議狀態(tài)一致

@ControllerAdvice
public class GlobalResponseAdvice implements ResponseBodyAdvice {

    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body,
                                  MethodParameter methodParameter,
                                  MediaType mediaType,
                                  Class aClass,
                                  ServerHttpRequest serverHttpRequest,
                                  ServerHttpResponse serverHttpResponse) {
        //如果響應(yīng)結(jié)果是JSON數(shù)據(jù)類型
        if(mediaType.equalsTypeAndSubtype(MediaType.APPLICATION_JSON)) {
            if(body instanceof ApiResponse) {
                ApiResponse apiResponse = (ApiResponse)body;
                if(apiResponse.getCode() != 999) { //999 不是標準的HTTP狀態(tài)碼谐岁,特殊處理
                    serverHttpResponse.setStatusCode(HttpStatus.valueOf(apiResponse.getCode()));
                }
              return body;
            } else {
                serverHttpResponse.setStatusCode(HttpStatus.OK);
                return ApiResponse.success(body);
            }
        }
        return body;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子伊佃,更是在濱河造成了極大的恐慌窜司,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件航揉,死亡現(xiàn)場離奇詭異塞祈,居然都是意外死亡,警方通過查閱死者的電腦和手機帅涂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門议薪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人媳友,你說我怎么就攤上這事斯议。” “怎么了醇锚?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵哼御,是天一觀的道長。 經(jīng)常有香客問我焊唬,道長恋昼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任求晶,我火速辦了婚禮焰雕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘芳杏。我一直安慰自己矩屁,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布爵赵。 她就那樣靜靜地躺著吝秕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪空幻。 梳的紋絲不亂的頭發(fā)上烁峭,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機與錄音秕铛,去河邊找鬼约郁。 笑死,一個胖子當(dāng)著我的面吹牛但两,可吹牛的內(nèi)容都是我干的鬓梅。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼谨湘,長吁一口氣:“原來是場噩夢啊……” “哼绽快!你這毒婦竟也來了芥丧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤坊罢,失蹤者是張志新(化名)和其女友劉穎续担,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體活孩,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡物遇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了诱鞠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挎挖。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖航夺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情崔涂,我是刑警寧澤阳掐,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站冷蚂,受9級特大地震影響缭保,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蝙茶,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一艺骂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧隆夯,春花似錦钳恕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至愧口,卻和暖如春睦番,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背耍属。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工托嚣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厚骗。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓示启,卻偏偏與公主長得像,于是被迫代替她去往敵國和親溯捆。 傳聞我的和親對象是個殘疾皇子丑搔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容