在 SpringBoot 中的異常處理

本文概述

本文將完成一個(gè) springboot 中異常處理的小 demo承绸,將使用 try-catch 和@RestControllerAdvice 兩種方法。本文代碼地址挣轨。

demo 概述

需求很簡(jiǎn)單訪問(wèn) http://localhost:8080/{id} 接口军熏,如果id是3則拋出用戶不存在的異常,如果id小于等于0則拋出參數(shù)異常刃唐。為了方便閱讀羞迷,我們先定義一些類和函數(shù)界轩。

定義HelloController

@RestController
public class HelloController {
    @Autowired
    private UserInfoManager userInfoManager;

    @GetMapping("/{id}")
    public String index(@PathVariable("id") long id) {
        return userInfoManager.getUserById(id);
    }
}

定義 ServiceException画饥,這是所有自定義異常的父類。

public class ServiceException extends RuntimeException {
    private int statusCode;
    private ServiceException.ErrorType errorType;
    private String code;

    public enum ErrorType {
        Client,
        Service,
        Unknown
    }
}

定義 ResourceNotFoundException

public class ResourceNotFoundException extends ServiceException {
    public ResourceNotFoundException(String message) {
        super(message);
        this.setStatusCode(HttpStatus.NOT_FOUND.value());
        this.setCode("USER_INFO_NOT_FOUND");
        this.setErrorType(ErrorType.Client);
    }
}

在 UserInfoManager 中浊猾,如果查找的用戶的id是3我們拋出 ResourceNotFoundException 的異常抖甘。

@Component
public class UserInfoManager {
    public String getUserById(long id) {
        if (id == 3L) {
            throw new ResourceNotFoundException("user 3 is not found");
        }
        return "This is user " + id;
    }
}

使用try-catch處理異常

此時(shí),我們?cè)L問(wèn) http://localhost:8080/3 葫慎,會(huì)得到以下的 response衔彻,這是spring 自帶的 response。

{
    "timestamp": "2021-03-03T13:24:21.217+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "message": "",
    "path": "/3"
}

如何修改 status 呢偷办?很簡(jiǎn)單艰额,加上注釋 @ResponseStatus(code = xxx)

@ResponseStatus(code = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends ServiceException {
    public ResourceNotFoundException(String message) {
        super(message);
        this.setStatusCode(HttpStatus.NOT_FOUND.value());
        this.setCode("USER_INFO_NOT_FOUND");
        this.setErrorType(ErrorType.Client);
    }
}

再次訪問(wèn)接口,我們得到了以下的 response椒涯。

{
    "timestamp": "2021-03-03T13:28:50.473+00:00",
    "status": 404,
    "error": "Not Found",
    "message": "",
    "path": "/3"
}

springboot 自帶的異常處理返回的 response 往往不能滿足我們的需求柄沮,如何產(chǎn)生自己的 response 呢?首先是一種比較傳統(tǒng)的方法废岂,我們使用try-catch 在 HelloController 中抓住 ResourceNotFoundException 異常祖搓,并返回一個(gè) ResponseEntity,其body內(nèi)容是我們定義的一個(gè)處理錯(cuò)誤返回的類 ErrorResponse湖苞。

public class ErrorResponse {
    private String code;
    private String message;
    private int statusCode;
    private ServiceException.ErrorType errorType;
}
@RestController
public class HelloController {
    @Autowired
    private UserInfoManager userInfoManager;

    @GetMapping("/{id}")
    public ResponseEntity<?> index(@PathVariable("id") long id) {
        try {
            return ResponseEntity.ok(userInfoManager.getUserById(id));
        } catch (ResourceNotFoundException ex) {
            ErrorResponse errorResponse = new ErrorResponse();
            errorResponse.setCode(ex.getCode());
            errorResponse.setErrorType(ex.getErrorType());
            errorResponse.setMessage(ex.getMessage());
            errorResponse.setStatusCode(ex.getStatusCode());
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(errorResponse);
        }
    }

}

再次訪問(wèn)接口拯欧,返回了我們想要的結(jié)果。但是這種 try-catch 的方法非常麻煩财骨,而且代碼非常的丑镐作。

{
    "code": "USER_INFO_NOT_FOUND",
    "message": "user 3 is not found",
    "statusCode": 404,
    "errorType": "Client"
}

使用 @RestControllerAdvice 進(jìn)行異常統(tǒng)一處理

我們使用注解 @RestControllerAdvice 對(duì) controller 進(jìn)行增強(qiáng),使用 @ExceptionHandler(Class) 對(duì)異常進(jìn)行處理隆箩。其實(shí)函數(shù)內(nèi)容和上文中try-catch代碼塊是一摸一樣的该贾。只是 springboot 給我們帶來(lái)了更為方便的操作,他將 controller 進(jìn)行增強(qiáng)摘仅,代我們處理了所有異常靶庙。

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ResourceNotFoundException.class)
    ResponseEntity<?> handleResourceNotFoundException(ResourceNotFoundException ex) {
        ErrorResponse errorResponse = new ErrorResponse();
        errorResponse.setCode(ex.getCode());
        errorResponse.setErrorType(ex.getErrorType());
        errorResponse.setMessage(ex.getMessage());
        errorResponse.setStatusCode(ex.getStatusCode());
        return ResponseEntity.status(ex.getStatusCode())
                .body(errorResponse);
    }
}

此時(shí)的 HelloConroller也變得異常簡(jiǎn)單。

@RestController
public class HelloController {
    @Autowired
    private UserInfoManager userInfoManager;

    @GetMapping("/{id}")
    public ResponseEntity<?> index(@PathVariable("id") long id) {
        return ResponseEntity.ok(userInfoManager.getUserById(id));
    }

}

如果還有其他的異常,我們還需要寫很多個(gè)帶有 @ExceptionHandler 的函數(shù)嗎六荒?DO NOT REPEAT YOURSELF护姆!很顯然是不需要的。任何需要子類的地方掏击,我們都可以傳他的父類卵皂。所以我們所有的自定義異常都只要繼承 ServiceException 就好了。

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ServiceException.class)
    ResponseEntity<?> handleServiceException(ServiceException ex) {
        ErrorResponse errorResponse = new ErrorResponse();
        errorResponse.setCode(ex.getCode());
        errorResponse.setErrorType(ex.getErrorType());
        errorResponse.setMessage(ex.getMessage());
        errorResponse.setStatusCode(ex.getStatusCode());
        return ResponseEntity.status(ex.getStatusCode())
                .body(errorResponse);
    }
}

測(cè)試一下吧砚亭,我們定義一個(gè)新的異常 InvalidParameterException灯变。

@ResponseStatus(code = HttpStatus.BAD_REQUEST)
public class InvalidParameterException extends ServiceException {
    public InvalidParameterException(String message) {
        super(message);
        this.setStatusCode(HttpStatus.BAD_REQUEST.value());
        this.setErrorType(ErrorType.Client);
        this.setCode("INVALID_PARAMETER");
    }
}

在用戶id小于等于0的時(shí)候拋出 InvalidParameterException,修改 UserInfoManager捅膘。

@Component
public class UserInfoManager {
    public String getUserById(long id) {
        if (id == 3L) {
            throw new ResourceNotFoundException("user 3 is not found");
        }
        if (id <= 0L) {
            throw new InvalidParameterException(String.format("user %s is invalid", id));
        }
        return "This is user " + id;
    }
}

訪問(wèn)一個(gè)異常接口 http://localhost:8080/0添祸,得到了想要的返回。拋出異逞罢蹋可以在 manage 層刃泌,也可以在 controller 層,這是無(wú)所謂的署尤,因?yàn)楫惓?huì)自下而上往上拋出耙替,最終都會(huì)在 controller 層被發(fā)現(xiàn)并處理。

{
    "code": "INVALID_PARAMETER",
    "message": "user 0 is invalid",
    "statusCode": 400,
    "errorType": "Client"
}

完曹体。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末俗扇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子箕别,更是在濱河造成了極大的恐慌铜幽,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件究孕,死亡現(xiàn)場(chǎng)離奇詭異啥酱,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)厨诸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門镶殷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人微酬,你說(shuō)我怎么就攤上這事绘趋。” “怎么了颗管?”我有些...
    開(kāi)封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵陷遮,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我垦江,道長(zhǎng)帽馋,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮绽族,結(jié)果婚禮上姨涡,老公的妹妹穿的比我還像新娘。我一直安慰自己吧慢,他們只是感情好涛漂,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著检诗,像睡著了一般匈仗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逢慌,一...
    開(kāi)封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天悠轩,我揣著相機(jī)與錄音,去河邊找鬼涕癣。 笑死哗蜈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的坠韩。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼炼列,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼只搁!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起俭尖,我...
    開(kāi)封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤氢惋,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后稽犁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體焰望,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年已亥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了熊赖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡虑椎,死狀恐怖震鹉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捆姜,我是刑警寧澤传趾,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站泥技,受9級(jí)特大地震影響浆兰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一簸呈、第九天 我趴在偏房一處隱蔽的房頂上張望宽涌。 院中可真熱鬧,春花似錦蝶棋、人聲如沸卸亮。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)兼贸。三九已至,卻和暖如春吃溅,著一層夾襖步出監(jiān)牢的瞬間溶诞,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工决侈, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留螺垢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓赖歌,卻偏偏與公主長(zhǎng)得像枉圃,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子庐冯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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