1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        我已經(jīng)不用 try catch 處理異常!太酸菜了!

        共 20972字,需瀏覽 42分鐘

         ·

        2022-03-18 23:10

        來(lái)源:cnblogs.com/jurendage/p/11255197.html

        程序汪友情提醒程序員們少吃老壇酸菜泡面,注意健康

        背景

        軟件開(kāi)發(fā)過(guò)程中,不可避免的是需要處理各種異常,就我自己來(lái)說(shuō),至少有一半以上的時(shí)間都是在處理各種異常情況,所以代碼中就會(huì)出現(xiàn)大量的try {...} catch {...} finally {...}?代碼塊,不僅有大量的冗余代碼,而且還影響代碼的可讀性。

        比較下面兩張圖,看看您現(xiàn)在編寫(xiě)的代碼屬于哪一種風(fēng)格?然后哪種編碼風(fēng)格您更喜歡?

        丑陋的 try catch 代碼塊

        優(yōu)雅的Controller

        上面的示例,還只是在Controller層,如果是在Service層,可能會(huì)有更多的try catch代碼塊。這將會(huì)嚴(yán)重影響代碼的可讀性、“美觀(guān)性”。

        所以如果是我的話(huà),我肯定偏向于第二種,我可以把更多的精力放在業(yè)務(wù)代碼的開(kāi)發(fā),同時(shí)代碼也會(huì)變得更加簡(jiǎn)潔。

        既然業(yè)務(wù)代碼不顯式地對(duì)異常進(jìn)行捕獲、處理,而異??隙ㄟ€是處理的,不然系統(tǒng)豈不是動(dòng)不動(dòng)就崩潰了,所以必須得有其他地方捕獲并處理這些異常。

        那么問(wèn)題來(lái)了,如何優(yōu)雅的處理各種異常?

        什么是統(tǒng)一異常處理

        Spring在3.2版本增加了一個(gè)注解@ControllerAdvice,可以與@ExceptionHandler、@InitBinder、@ModelAttribute?等注解注解配套使用。

        對(duì)于這幾個(gè)注解的作用,這里不做過(guò)多贅述,若有不了解的,可以參考Spring3.2新注解@ControllerAdvice,先大概有個(gè)了解。關(guān)注微信公眾號(hào):Java項(xiàng)目分享,在后臺(tái)回復(fù):項(xiàng)目分享,可以獲取我整理的 N 篇 項(xiàng)目資源,都是干貨。

        不過(guò)跟異常處理相關(guān)的只有注解@ExceptionHandler,從字面上看,就是?異常處理器?的意思,其實(shí)際作用也是:若在某個(gè)Controller類(lèi)定義一個(gè)異常處理方法,并在方法上添加該注解,那么當(dāng)出現(xiàn)指定的異常時(shí),會(huì)執(zhí)行該處理異常的方法,其可以使用springmvc提供的數(shù)據(jù)綁定,比如注入HttpServletRequest等,還可以接受一個(gè)當(dāng)前拋出的Throwable對(duì)象。

        但是,這樣一來(lái),就必須在每一個(gè)Controller類(lèi)都定義一套這樣的異常處理方法,因?yàn)楫惓?梢允歉鞣N各樣。這樣一來(lái),就會(huì)造成大量的冗余代碼,而且若需要新增一種異常的處理邏輯,就必須修改所有Controller類(lèi)了,很不優(yōu)雅。

        當(dāng)然你可能會(huì)說(shuō),那就定義個(gè)類(lèi)似BaseController的基類(lèi),這樣總行了吧。

        這種做法雖然沒(méi)錯(cuò),但仍不盡善盡美,因?yàn)檫@樣的代碼有一定的侵入性和耦合性。簡(jiǎn)簡(jiǎn)單單的Controller,我為啥非得繼承這樣一個(gè)類(lèi)呢,萬(wàn)一已經(jīng)繼承其他基類(lèi)了呢。大家都知道Java只能繼承一個(gè)類(lèi)。

        那有沒(méi)有一種方案,既不需要跟Controller耦合,也可以將定義的?異常處理器?應(yīng)用到所有控制器呢?所以注解@ControllerAdvice出現(xiàn)了,簡(jiǎn)單的說(shuō),該注解可以把異常處理器應(yīng)用到所有控制器,而不是單個(gè)控制器。

        借助該注解,我們可以實(shí)現(xiàn):在獨(dú)立的某個(gè)地方,比如單獨(dú)一個(gè)類(lèi),定義一套對(duì)各種異常的處理機(jī)制,然后在類(lèi)的簽名加上注解@ControllerAdvice,統(tǒng)一對(duì)?不同階段的、不同異常?進(jìn)行處理。這就是統(tǒng)一異常處理的原理。

        注意到上面對(duì)異常按階段進(jìn)行分類(lèi),大體可以分成:進(jìn)入Controller前的異常 和?Service?層異常,具體可以參考下圖:

        目標(biāo)

        消滅95%以上的?try catch?代碼塊,以?xún)?yōu)雅的?Assert(斷言) 方式來(lái)校驗(yàn)業(yè)務(wù)的異常情況,只關(guān)注業(yè)務(wù)邏輯,而不用花費(fèi)大量精力寫(xiě)冗余的?try catch?代碼塊。

        統(tǒng)一異常處理實(shí)戰(zhàn)

        在定義統(tǒng)一異常處理類(lèi)之前,先來(lái)介紹一下如何優(yōu)雅的判定異常情況并拋異常。

        用 Assert(斷言) 替換 throw exception

        想必?Assert(斷言)?大家都很熟悉,比如?Spring?家族的?org.springframework.util.Assert,在我們寫(xiě)測(cè)試用例的時(shí)候經(jīng)常會(huì)用到,使用斷言能讓我們編碼的時(shí)候有一種非一般絲滑的感覺(jué),比如:

        @Test
        ????public?void?test1()?{
        ????????...
        ????????User?user?=?userDao.selectById(userId);
        ????????Assert.notNull(user,?"用戶(hù)不存在.");
        ????????...
        ????}

        ????@Test
        ????public?void?test2()?{
        ????????//?另一種寫(xiě)法
        ????????User?user?=?userDao.selectById(userId);
        ????????if?(user?==?null)?{
        ????????????throw?new?IllegalArgumentException("用戶(hù)不存在.");
        ????????}
        ????}

        有沒(méi)有感覺(jué)第一種判定非空的寫(xiě)法很優(yōu)雅,第二種寫(xiě)法則是相對(duì)丑陋的?if {...}?代碼塊。那么神奇的?Assert.notNull()?背后到底做了什么呢?下面是?Assert?的部分源碼:

        public?abstract?class?Assert?{
        ????public?Assert()?{
        ????}

        ????public?static?void?notNull(@Nullable?Object?object,?String?message)?{
        ????????if?(object?==?null)?{
        ????????????throw?new?IllegalArgumentException(message);
        ????????}
        ????}
        }

        可以看到,Assert?其實(shí)就是幫我們把?if {...}?封裝了一下,是不是很神奇。雖然很簡(jiǎn)單,但不可否認(rèn)的是編碼體驗(yàn)至少提升了一個(gè)檔次。那么我們能不能模仿org.springframework.util.Assert,也寫(xiě)一個(gè)斷言類(lèi),不過(guò)斷言失敗后拋出的異常不是IllegalArgumentException?這些內(nèi)置異常,而是我們自己定義的異常。下面讓我們來(lái)嘗試一下。

        Assert
        public?interface?Assert?{
        ????/**
        ?????*?創(chuàng)建異常
        ?????*?@param?args
        ?????*?@return
        ?????*/

        ????BaseException?newException(Object...?args);

        ????/**
        ?????*?創(chuàng)建異常
        ?????*?@param?t
        ?????*?@param?args
        ?????*?@return
        ?????*/

        ????BaseException?newException(Throwable?t,?Object...?args);

        ????/**
        ?????*?

        斷言對(duì)象obj非空。如果對(duì)象obj為空,則拋出異常
        ?????*
        ?????*?@param?obj?待判斷對(duì)象
        ?????*/
        ????default?void?assertNotNull(Object?obj)?{
        ????????if?(obj?==?null)?{
        ????????????throw?newException(obj);
        ????????}
        ????}

        ????/**
        ?????*?

        斷言對(duì)象obj非空。如果對(duì)象obj為空,則拋出異常
        ?????*?

        異常信息message支持傳遞參數(shù)方式,避免在判斷之前進(jìn)行字符串拼接操作
        ?????*
        ?????*?@param?obj?待判斷對(duì)象
        ?????*?@param?args?message占位符對(duì)應(yīng)的參數(shù)列表
        ?????*/
        ????default?void?assertNotNull(Object?obj,?Object...?args)?{
        ????????if?(obj?==?null)?{
        ????????????throw?newException(args);
        ????????}
        ????}
        }

        上面的Assert斷言方法是使用接口的默認(rèn)方法定義的,然后有沒(méi)有發(fā)現(xiàn)當(dāng)斷言失敗后,拋出的異常不是具體的某個(gè)異常,而是交由2個(gè)newException接口方法提供。

        因?yàn)闃I(yè)務(wù)邏輯中出現(xiàn)的異常基本都是對(duì)應(yīng)特定的場(chǎng)景,比如根據(jù)用戶(hù)id獲取用戶(hù)信息,查詢(xún)結(jié)果為null,此時(shí)拋出的異??赡転?code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(71, 193, 168);">UserNotFoundException,并且有特定的異常碼(比如7001)和異常信息“用戶(hù)不存在”。所以具體拋出什么異常,有Assert的實(shí)現(xiàn)類(lèi)決定。

        看到這里,您可能會(huì)有這樣的疑問(wèn),按照上面的說(shuō)法,那豈不是有多少異常情況,就得有定義等量的斷言類(lèi)和異常類(lèi),這顯然是反人類(lèi)的,這也沒(méi)想象中高明嘛。別急,且聽(tīng)我細(xì)細(xì)道來(lái)。

        善解人意的Enum

        自定義異常BaseException有2個(gè)屬性,即codemessage,這樣一對(duì)屬性,有沒(méi)有想到什么類(lèi)一般也會(huì)定義這2個(gè)屬性?沒(méi)錯(cuò),就是枚舉類(lèi)。且看我如何將?Enum?和?Assert?結(jié)合起來(lái),相信我一定會(huì)讓你眼前一亮。如下:

        public?interface?IResponseEnum?{
        ????int?getCode();
        ????String?getMessage();
        }
        /**
        ?*?

        業(yè)務(wù)異常


        ?*?

        業(yè)務(wù)處理時(shí),出現(xiàn)異常,可以?huà)伋鲈摦惓?/p>
        ?*/
        public?class?BusinessException?extends??BaseException?{

        ????private?static?final?long?serialVersionUID?=?1L;

        ????public?BusinessException(IResponseEnum?responseEnum,?Object[]?args,?String?message)?{
        ????????super(responseEnum,?args,?message);
        ????}

        ????public?BusinessException(IResponseEnum?responseEnum,?Object[]?args,?String?message,?Throwable?cause)?{
        ????????super(responseEnum,?args,?message,?cause);
        ????}
        }
        public?interface?BusinessExceptionAssert?extends?IResponseEnum,?Assert?{

        ????@Override
        ????default?BaseException?newException(Object...?args)?{
        ????????String?msg?=?MessageFormat.format(this.getMessage(),?args);

        ????????return?new?BusinessException(this,?args,?msg);
        ????}

        ????@Override
        ????default?BaseException?newException(Throwable?t,?Object...?args)?{
        ????????String?msg?=?MessageFormat.format(this.getMessage(),?args);

        ????????return?new?BusinessException(this,?args,?msg,?t);
        ????}

        }
        @Getter
        @AllArgsConstructor
        public?enum?ResponseEnum?implements?BusinessExceptionAssert?{

        ????/**
        ?????*?Bad?licence?type
        ?????*/

        ????BAD_LICENCE_TYPE(7001,?"Bad?licence?type."),
        ????/**
        ?????*?Licence?not?found
        ?????*/

        ????LICENCE_NOT_FOUND(7002,?"Licence?not?found.")
        ????;

        ????/**
        ?????*?返回碼
        ?????*/

        ????private?int?code;
        ????/**
        ?????*?返回消息
        ?????*/

        ????private?String?message;
        }

        看到這里,有沒(méi)有眼前一亮的感覺(jué),代碼示例中定義了兩個(gè)枚舉實(shí)例:BAD_LICENCE_TYPE、LICENCE_NOT_FOUND,分別對(duì)應(yīng)了BadLicenceTypeException、LicenceNotFoundException兩種異常。

        以后每增加一種異常情況,只需增加一個(gè)枚舉實(shí)例即可,再也不用每一種異常都定義一個(gè)異常類(lèi)了。然后再來(lái)看下如何使用,假設(shè)LicenceService有校驗(yàn)Licence是否存在的方法,如下:

        /**
        ?????*?校驗(yàn){@link?Licence}存在
        ?????*?@param?licence
        ?????*/

        ????private?void?checkNotNull(Licence?licence)?{
        ????????ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence);
        ????}

        若不使用斷言,代碼可能如下:

        private?void?checkNotNull(Licence?licence)?{
        ????????if?(licence?==?null)?{
        ????????????throw?new?LicenceNotFoundException();
        ????????????//?或者這樣
        ????????????throw?new?BusinessException(7001,?"Bad?licence?type.");
        ????????}
        ????}

        使用枚舉類(lèi)結(jié)合(繼承)Assert,只需根據(jù)特定的異常情況定義不同的枚舉實(shí)例,如上面的BAD_LICENCE_TYPE、LICENCE_NOT_FOUND,就能夠針對(duì)不同情況拋出特定的異常(這里指攜帶特定的異常碼和異常消息),這樣既不用定義大量的異常類(lèi),同時(shí)還具備了斷言的良好可讀性,當(dāng)然這種方案的好處遠(yuǎn)不止這些,請(qǐng)繼續(xù)閱讀后文,慢慢體會(huì)。

        注:上面舉的例子是針對(duì)特定的業(yè)務(wù),而有部分異常情況是通用的,比如:服務(wù)器繁忙、網(wǎng)絡(luò)異常、服務(wù)器異常、參數(shù)校驗(yàn)異常、404等,所以有CommonResponseEnum、ArgumentResponseEnum、ServletResponseEnum,其中?ServletResponseEnum?會(huì)在后文詳細(xì)說(shuō)明。

        定義統(tǒng)一異常處理器類(lèi)

        @Slf4j
        @Component
        @ControllerAdvice
        @ConditionalOnWebApplication
        @ConditionalOnMissingBean(UnifiedExceptionHandler.class)
        public?class?UnifiedExceptionHandler?
        {
        ????/**
        ?????*?生產(chǎn)環(huán)境
        ?????*/

        ????private?final?static?String?ENV_PROD?=?"prod";

        ????@Autowired
        ????private?UnifiedMessageSource?unifiedMessageSource;

        ????/**
        ?????*?當(dāng)前環(huán)境
        ?????*/

        ????@Value("${spring.profiles.active}")
        ????private?String?profile;

        ????/**
        ?????*?獲取國(guó)際化消息
        ?????*
        ?????*?@param?e?異常
        ?????*?@return
        ?????*/

        ????public?String?getMessage(BaseException?e)?{
        ????????String?code?=?"response."?+?e.getResponseEnum().toString();
        ????????String?message?=?unifiedMessageSource.getMessage(code,?e.getArgs());

        ????????if?(message?==?null?||?message.isEmpty())?{
        ????????????return?e.getMessage();
        ????????}

        ????????return?message;
        ????}

        ????/**
        ?????*?業(yè)務(wù)異常
        ?????*
        ?????*?@param?e?異常
        ?????*?@return?異常結(jié)果
        ?????*/

        ????@ExceptionHandler(value?=?BusinessException.class)
        ????@ResponseBody
        ????public?ErrorResponse?handleBusinessException(BaseException?e)?
        {
        ????????log.error(e.getMessage(),?e);

        ????????return?new?ErrorResponse(e.getResponseEnum().getCode(),?getMessage(e));
        ????}

        ????/**
        ?????*?自定義異常
        ?????*
        ?????*?@param?e?異常
        ?????*?@return?異常結(jié)果
        ?????*/

        ????@ExceptionHandler(value?=?BaseException.class)
        ????@ResponseBody
        ????public?ErrorResponse?handleBaseException(BaseException?e)?
        {
        ????????log.error(e.getMessage(),?e);

        ????????return?new?ErrorResponse(e.getResponseEnum().getCode(),?getMessage(e));
        ????}

        ????/**
        ?????*?Controller上一層相關(guān)異常
        ?????*
        ?????*?@param?e?異常
        ?????*?@return?異常結(jié)果
        ?????*/

        ????@ExceptionHandler({
        ????????????NoHandlerFoundException.class,
        ????????????HttpRequestMethodNotSupportedException.class,
        ????????????HttpMediaTypeNotSupportedException.class,
        ????????????MissingPathVariableException.class,
        ????????????MissingServletRequestParameterException.class,
        ????????????TypeMismatchException.class,
        ????????????HttpMessageNotReadableException.class,
        ????????????HttpMessageNotWritableException.class,
        ????????????//?BindException.class,
        ????????????//?MethodArgumentNotValidException.class
        ????????????HttpMediaTypeNotAcceptableException.class,
        ????????????ServletRequestBindingException.class,
        ????????????ConversionNotSupportedException.class,
        ????????????MissingServletRequestPartException.class,
        ????????????AsyncRequestTimeoutException.class
        ????})
        ????@ResponseBody
        ????public?ErrorResponse?handleServletException(Exception?e)?
        {
        ????????log.error(e.getMessage(),?e);
        ????????int?code?=?CommonResponseEnum.SERVER_ERROR.getCode();
        ????????try?{
        ????????????ServletResponseEnum?servletExceptionEnum?=?ServletResponseEnum.valueOf(e.getClass().getSimpleName());
        ????????????code?=?servletExceptionEnum.getCode();
        ????????}?catch?(IllegalArgumentException?e1)?{
        ????????????log.error("class?[{}]?not?defined?in?enum?{}",?e.getClass().getName(),?ServletResponseEnum.class.getName());
        ????????}

        ????????if?(ENV_PROD.equals(profile))?{
        ????????????//?當(dāng)為生產(chǎn)環(huán)境,?不適合把具體的異常信息展示給用戶(hù),?比如404.
        ????????????code?=?CommonResponseEnum.SERVER_ERROR.getCode();
        ????????????BaseException?baseException?=?new?BaseException(CommonResponseEnum.SERVER_ERROR);
        ????????????String?message?=?getMessage(baseException);
        ????????????return?new?ErrorResponse(code,?message);
        ????????}

        ????????return?new?ErrorResponse(code,?e.getMessage());
        ????}


        ????/**
        ?????*?參數(shù)綁定異常
        ?????*
        ?????*?@param?e?異常
        ?????*?@return?異常結(jié)果
        ?????*/

        ????@ExceptionHandler(value?=?BindException.class)
        ????@ResponseBody
        ????public?ErrorResponse?handleBindException(BindException?e)?
        {
        ????????log.error("參數(shù)綁定校驗(yàn)異常",?e);

        ????????return?wrapperBindingResult(e.getBindingResult());
        ????}

        ????/**
        ?????*?參數(shù)校驗(yàn)異常,將校驗(yàn)失敗的所有異常組合成一條錯(cuò)誤信息
        ?????*
        ?????*?@param?e?異常
        ?????*?@return?異常結(jié)果
        ?????*/

        ????@ExceptionHandler(value?=?MethodArgumentNotValidException.class)
        ????@ResponseBody
        ????public?ErrorResponse?handleValidException(MethodArgumentNotValidException?e)?
        {
        ????????log.error("參數(shù)綁定校驗(yàn)異常",?e);

        ????????return?wrapperBindingResult(e.getBindingResult());
        ????}

        ????/**
        ?????*?包裝綁定異常結(jié)果
        ?????*
        ?????*?@param?bindingResult?綁定結(jié)果
        ?????*?@return?異常結(jié)果
        ?????*/

        ????private?ErrorResponse?wrapperBindingResult(BindingResult?bindingResult)?{
        ????????StringBuilder?msg?=?new?StringBuilder();

        ????????for?(ObjectError?error?:?bindingResult.getAllErrors())?{
        ????????????msg.append(",?");
        ????????????if?(error?instanceof?FieldError)?{
        ????????????????msg.append(((FieldError)?error).getField()).append(":?");
        ????????????}
        ????????????msg.append(error.getDefaultMessage()?==?null???""?:?error.getDefaultMessage());

        ????????}

        ????????return?new?ErrorResponse(ArgumentResponseEnum.VALID_ERROR.getCode(),?msg.substring(2));
        ????}

        ????/**
        ?????*?未定義異常
        ?????*
        ?????*?@param?e?異常
        ?????*?@return?異常結(jié)果
        ?????*/

        ????@ExceptionHandler(value?=?Exception.class)
        ????@ResponseBody
        ????public?ErrorResponse?handleException(Exception?e)?
        {
        ????????log.error(e.getMessage(),?e);

        ????????if?(ENV_PROD.equals(profile))?{
        ????????????//?當(dāng)為生產(chǎn)環(huán)境,?不適合把具體的異常信息展示給用戶(hù),?比如數(shù)據(jù)庫(kù)異常信息.
        ????????????int?code?=?CommonResponseEnum.SERVER_ERROR.getCode();
        ????????????BaseException?baseException?=?new?BaseException(CommonResponseEnum.SERVER_ERROR);
        ????????????String?message?=?getMessage(baseException);
        ????????????return?new?ErrorResponse(code,?message);
        ????????}

        ????????return?new?ErrorResponse(CommonResponseEnum.SERVER_ERROR.getCode(),?e.getMessage());
        ????}

        }

        可以看到,上面將異常分成幾類(lèi),實(shí)際上只有兩大類(lèi),一類(lèi)是ServletException、ServiceException,還記得上文提到的?按階段分類(lèi)?嗎,即對(duì)應(yīng) 進(jìn)入Controller前的異常 和?Service?層異常;然后?ServiceException?再分成自定義異常、未知異常。對(duì)應(yīng)關(guān)系如下:

        • 進(jìn)入Controller前的異常: handleServletException、handleBindException、handleValidException
        • 自定義異常: handleBusinessException、handleBaseException
        • 未知異常: handleException

        接下來(lái)分別對(duì)這幾種異常處理器做詳細(xì)說(shuō)明。

        異常處理器說(shuō)明

        handleServletException

        一個(gè)http請(qǐng)求,在到達(dá)Controller前,會(huì)對(duì)該請(qǐng)求的請(qǐng)求信息與目標(biāo)控制器信息做一系列校驗(yàn)。這里簡(jiǎn)單說(shuō)一下:

        • NoHandlerFoundException

          首先根據(jù)請(qǐng)求Url查找有沒(méi)有對(duì)應(yīng)的控制器,若沒(méi)有則會(huì)拋該異常,也就是大家非常熟悉的404異常;

        • HttpRequestMethodNotSupportedException

          若匹配到了(匹配結(jié)果是一個(gè)列表,不同的是http方法不同,如:Get、Post等),則嘗試將請(qǐng)求的http方法與列表的控制器做匹配,若沒(méi)有對(duì)應(yīng)http方法的控制器,則拋該異常;

        • HttpMediaTypeNotSupportedException

          然后再對(duì)請(qǐng)求頭與控制器支持的做比較,比如content-type請(qǐng)求頭,若控制器的參數(shù)簽名包含注解@RequestBody,但是請(qǐng)求的content-type請(qǐng)求頭的值沒(méi)有包含application/json,那么會(huì)拋該異常(當(dāng)然,不止這種情況會(huì)拋這個(gè)異常);

        • MissingPathVariableException

          未檢測(cè)到路徑參數(shù)。比如url為:/licence/{licenceId},參數(shù)簽名包含@PathVariable("licenceId"),當(dāng)請(qǐng)求的url為/licence,在沒(méi)有明確定義url為/licence的情況下,會(huì)被判定為:缺少路徑參數(shù);

        • MissingServletRequestParameterException

          缺少請(qǐng)求參數(shù)。比如定義了參數(shù)@RequestParam(“l(fā)icenceId”) String licenceId,但發(fā)起請(qǐng)求時(shí),未攜帶該參數(shù),則會(huì)拋該異常;

        • TypeMismatchException

          參數(shù)類(lèi)型匹配失敗。比如:接收參數(shù)為L(zhǎng)ong型,但傳入的值確是一個(gè)字符串,那么將會(huì)出現(xiàn)類(lèi)型轉(zhuǎn)換失敗的情況,這時(shí)會(huì)拋該異常;

        • HttpMessageNotReadableException

          與上面的HttpMediaTypeNotSupportedException舉的例子完全相反,即請(qǐng)求頭攜帶了"content-type: application/json;charset=UTF-8",但接收參數(shù)卻沒(méi)有添加注解@RequestBody,或者請(qǐng)求體攜帶的 json 串反序列化成 pojo 的過(guò)程中失敗了,也會(huì)拋該異常;

        • HttpMessageNotWritableException

          返回的 pojo 在序列化成 json 過(guò)程失敗了,那么拋該異常;

        handleBindException

        參數(shù)校驗(yàn)異常,后文詳細(xì)說(shuō)明。

        handleValidException

        參數(shù)校驗(yàn)異常,后文詳細(xì)說(shuō)明。

        handleBusinessException、handleBaseException

        處理自定義的業(yè)務(wù)異常,只是handleBaseException處理的是除了?BusinessException?意外的所有業(yè)務(wù)異常。就目前來(lái)看,這2個(gè)是可以合并成一個(gè)的。

        handleException

        處理所有未知的異常,比如操作數(shù)據(jù)庫(kù)失敗的異常。

        注:上面的handleServletException、handleException?這兩個(gè)處理器,返回的異常信息,不同環(huán)境返回的可能不一樣,以為這些異常信息都是框架自帶的異常信息,一般都是英文的,不太好直接展示給用戶(hù)看,所以統(tǒng)一返回SERVER_ERROR代表的異常信息。

        異于常人的404

        上文提到,當(dāng)請(qǐng)求沒(méi)有匹配到控制器的情況下,會(huì)拋出NoHandlerFoundException異常,但其實(shí)默認(rèn)情況下不是這樣,默認(rèn)情況下會(huì)出現(xiàn)類(lèi)似如下頁(yè)面:

        Whitelabel Error Page

        這個(gè)頁(yè)面是如何出現(xiàn)的呢?實(shí)際上,當(dāng)出現(xiàn)404的時(shí)候,默認(rèn)是不拋異常的,而是?forward跳轉(zhuǎn)到/error控制器,spring也提供了默認(rèn)的error控制器,如下:

        那么,如何讓404也拋出異常呢,只需在properties文件中加入如下配置即可:

        spring.mvc.throw-exception-if-no-handler-found=true
        spring.resources.add-mappings=false

        如此,就可以異常處理器中捕獲它了,然后前端只要捕獲到特定的狀態(tài)碼,立即跳轉(zhuǎn)到404頁(yè)面即可

        捕獲404對(duì)應(yīng)的異常

        統(tǒng)一返回結(jié)果

        在驗(yàn)證統(tǒng)一異常處理器之前,順便說(shuō)一下統(tǒng)一返回結(jié)果。說(shuō)白了,其實(shí)是統(tǒng)一一下返回結(jié)果的數(shù)據(jù)結(jié)構(gòu)。code、message?是所有返回結(jié)果中必有的字段,而當(dāng)需要返回?cái)?shù)據(jù)時(shí),則需要另一個(gè)字段?data?來(lái)表示。

        所以首先定義一個(gè)?BaseResponse?來(lái)作為所有返回結(jié)果的基類(lèi);

        然后定義一個(gè)通用返回結(jié)果類(lèi)CommonResponse,繼承?BaseResponse,而且多了字段?data;

        為了區(qū)分成功和失敗返回結(jié)果,于是再定義一個(gè)?ErrorResponse

        最后還有一種常見(jiàn)的返回結(jié)果,即返回的數(shù)據(jù)帶有分頁(yè)信息,因?yàn)檫@種接口比較常見(jiàn),所以有必要單獨(dú)定義一個(gè)返回結(jié)果類(lèi)?QueryDataResponse,該類(lèi)繼承自?CommonResponse,只是把?data?字段的類(lèi)型限制為?QueryDdata,QueryDdata中定義了分頁(yè)信息相應(yīng)的字段,即totalCount、pageNo、?pageSize、records。

        其中比較常用的只有?CommonResponse?和?QueryDataResponse,但是名字又賊鬼死長(zhǎng),何不定義2個(gè)名字超簡(jiǎn)單的類(lèi)來(lái)替代呢?于是?R?和?QR?誕生了,以后返回結(jié)果的時(shí)候只需這樣寫(xiě):new R<>(data)、new QR<>(queryData)。

        所有的返回結(jié)果類(lèi)的定義這里就不貼出來(lái)了

        驗(yàn)證統(tǒng)一異常處理

        因?yàn)檫@一套統(tǒng)一異常處理可以說(shuō)是通用的,所有可以設(shè)計(jì)成一個(gè)?common包,以后每一個(gè)新項(xiàng)目/模塊只需引入該包即可。所以為了驗(yàn)證,需要新建一個(gè)項(xiàng)目,并引入該?common包。

        主要代碼

        下面是用于驗(yàn)證的主要源碼:

        @Service
        public?class?LicenceService?extends?ServiceImpl<LicenceMapper,?Licence>?{

        ????@Autowired
        ????private?OrganizationClient?organizationClient;

        ????/**
        ?????*?查詢(xún){@link?Licence}?詳情
        ?????*?@param?licenceId
        ?????*?@return
        ?????*/

        ????public?LicenceDTO?queryDetail(Long?licenceId)?{
        ????????Licence?licence?=?this.getById(licenceId);
        ????????checkNotNull(licence);

        ????????OrganizationDTO?org?=?ClientUtil.execute(()?->?organizationClient.getOrganization(licence.getOrganizationId()));
        ????????return?toLicenceDTO(licence,?org);
        ????}

        ????/**
        ?????*?分頁(yè)獲取
        ?????*?@param?licenceParam?分頁(yè)查詢(xún)參數(shù)
        ?????*?@return
        ?????*/

        ????public?QueryData?getLicences(LicenceParam?licenceParam)?{
        ????????String?licenceType?=?licenceParam.getLicenceType();
        ????????LicenceTypeEnum?licenceTypeEnum?=?LicenceTypeEnum.parseOfNullable(licenceType);
        ????????//?斷言,?非空
        ????????ResponseEnum.BAD_LICENCE_TYPE.assertNotNull(licenceTypeEnum);

        ????????LambdaQueryWrapper?wrapper?=?new?LambdaQueryWrapper<>();
        ????????wrapper.eq(Licence::getLicenceType,?licenceType);
        ????????IPage?page?=?this.page(new?QueryPage<>(licenceParam),?wrapper);
        ????????return?new?QueryData<>(page,?this::toSimpleLicenceDTO);
        ????}

        ????/**
        ?????*?新增{@link?Licence}
        ?????*?@param?request?請(qǐng)求體
        ?????*?@return
        ?????*/

        ????@Transactional(rollbackFor?=?Throwable.class)
        ????public?LicenceAddRespData?addLicence(LicenceAddRequest?request)?
        {
        ????????Licence?licence?=?new?Licence();
        ????????licence.setOrganizationId(request.getOrganizationId());
        ????????licence.setLicenceType(request.getLicenceType());
        ????????licence.setProductName(request.getProductName());
        ????????licence.setLicenceMax(request.getLicenceMax());
        ????????licence.setLicenceAllocated(request.getLicenceAllocated());
        ????????licence.setComment(request.getComment());
        ????????this.save(licence);

        ????????return?new?LicenceAddRespData(licence.getLicenceId());
        ????}

        ????/**
        ?????*?entity?->?simple?dto
        ?????*?@param?licence?{@link?Licence}?entity
        ?????*?@return?{@link?SimpleLicenceDTO}
        ?????*/

        ????private?SimpleLicenceDTO?toSimpleLicenceDTO(Licence?licence)?{
        ????????//?省略
        ????}

        ????/**
        ?????*?entity?->?dto
        ?????*?@param?licence?{@link?Licence}?entity
        ?????*?@param?org?{@link?OrganizationDTO}
        ?????*?@return?{@link?LicenceDTO}
        ?????*/

        ????private?LicenceDTO?toLicenceDTO(Licence?licence,?OrganizationDTO?org)?{
        ????????//?省略
        ????}

        ????/**
        ?????*?校驗(yàn){@link?Licence}存在
        ?????*?@param?licence
        ?????*/

        ????private?void?checkNotNull(Licence?licence)?{
        ????????ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence);
        ????}

        }

        PS: 這里使用的DAO框架是mybatis-plus。啟動(dòng)時(shí),自動(dòng)插入的數(shù)據(jù)為:

        --?licence
        INSERT?INTO?licence?(licence_id,?organization_id,?licence_type,?product_name,?licence_max,?licence_allocated)
        VALUES?(1,?1,?'user','CustomerPro',?100,5);
        INSERT?INTO?licence?(licence_id,?organization_id,?licence_type,?product_name,?licence_max,?licence_allocated)
        VALUES?(2,?1,?'user','suitability-plus',?200,189);
        INSERT?INTO?licence?(licence_id,?organization_id,?licence_type,?product_name,?licence_max,?licence_allocated)
        VALUES?(3,?2,?'user','HR-PowerSuite',?100,4);
        INSERT?INTO?licence?(licence_id,?organization_id,?licence_type,?product_name,?licence_max,?licence_allocated)
        VALUES?(4,?2,?'core-prod','WildCat?Application?Gateway',?16,16);

        --?organizations
        INSERT?INTO?organization?(id,?name,?contact_name,?contact_email,?contact_phone)
        VALUES?(1,?'customer-crm-co',?'Mark?Balster',?'[email protected]',?'823-555-1212');
        INSERT?INTO?organization?(id,?name,?contact_name,?contact_email,?contact_phone)
        VALUES?(2,?'HR-PowerSuite',?'Doug?Drewry','[email protected]',?'920-555-1212');
        開(kāi)始驗(yàn)證
        捕獲自定義異常
        1. 獲取不存在的?licence?詳情:http://localhost:10000/licence/5。成功響應(yīng)的請(qǐng)求:licenceId=1

          檢驗(yàn)非空

          捕獲 Licence not found 異常

          Licence not found

        2. 根據(jù)不存在的?licence type?獲取?licence?列表:http://localhost:10000/licence/list?licenceType=ddd??蛇x的?licence type?為:user、core-prod 。

          校驗(yàn)非空

          捕獲 Bad licence type 異常

          Bad licence type

        捕獲進(jìn)入 Controller 前的異常

        1. 訪(fǎng)問(wèn)不存在的接口:http://localhost:10000/licence/list/ddd

          捕獲404異常

        2. http 方法不支持:http://localhost:10000/licence

          PostMapping

          捕獲 Request method not supported 異常

          Request method not supported

        3. 校驗(yàn)異常1:http://localhost:10000/licence/list?licenceType=

          getLicences

          LicenceParam

          捕獲參數(shù)綁定校驗(yàn)異常

          licence type cannot be empty

        4. 校驗(yàn)異常2:post 請(qǐng)求,這里使用postman模擬。

          addLicence

          LicenceAddRequest

          請(qǐng)求url即結(jié)果

          捕獲參數(shù)綁定校驗(yàn)異常

          注:因?yàn)閰?shù)綁定校驗(yàn)異常的異常信息的獲取方式與其它異常不一樣,所以才把這2種情況的異常從?進(jìn)入 Controller 前的異常?單獨(dú)拆出來(lái),下面是異常信息的收集邏輯:

          異常信息的收集

        捕獲未知異常

        假設(shè)我們現(xiàn)在隨便對(duì)?Licence?新增一個(gè)字段?test,但不修改數(shù)據(jù)庫(kù)表結(jié)構(gòu),然后訪(fǎng)問(wèn):http://localhost:10000/licence/1。

        增加test字段

        捕獲數(shù)據(jù)庫(kù)異常

        Error querying database

        小結(jié)

        可以看到,測(cè)試的異常都能夠被捕獲,然后以?code、message?的形式返回。每一個(gè)項(xiàng)目/模塊,在定義業(yè)務(wù)異常的時(shí)候,只需定義一個(gè)枚舉類(lèi),然后實(shí)現(xiàn)接口?BusinessExceptionAssert,最后為每一種業(yè)務(wù)異常定義對(duì)應(yīng)的枚舉實(shí)例即可,而不用定義許多異常類(lèi)。使用的時(shí)候也很方便,用法類(lèi)似斷言。

        擴(kuò)展

        在生產(chǎn)環(huán)境,若捕獲到?未知異常?或者?ServletException,因?yàn)槎际且婚L(zhǎng)串的異常信息,若直接展示給用戶(hù)看,顯得不夠?qū)I(yè),于是,我們可以這樣做:當(dāng)檢測(cè)到當(dāng)前環(huán)境是生產(chǎn)環(huán)境,那么直接返回 “網(wǎng)絡(luò)異常”。

        生產(chǎn)環(huán)境返回“網(wǎng)絡(luò)異?!?/p>

        可以通過(guò)以下方式修改當(dāng)前環(huán)境:

        修改當(dāng)前環(huán)境為生產(chǎn)環(huán)境

        總結(jié)

        使用?斷言?和?枚舉類(lèi)?相結(jié)合的方式,再配合統(tǒng)一異常處理,基本大部分的異常都能夠被捕獲。

        為什么說(shuō)大部分異常,因?yàn)楫?dāng)引入?spring cloud security?后,還會(huì)有認(rèn)證/授權(quán)異常,網(wǎng)關(guān)的服務(wù)降級(jí)異常、跨模塊調(diào)用異常、遠(yuǎn)程調(diào)用第三方服務(wù)異常等,這些異常的捕獲方式與本文介紹的不太一樣,不過(guò)限于篇幅,這里不做詳細(xì)說(shuō)明,以后會(huì)有單獨(dú)的文章介紹。

        另外,當(dāng)需要考慮國(guó)際化的時(shí)候,捕獲異常后的異常信息一般不能直接返回,需要轉(zhuǎn)換成對(duì)應(yīng)的語(yǔ)言,不過(guò)本文已考慮到了這個(gè),獲取消息的時(shí)候已經(jīng)做了國(guó)際化映射,邏輯如下:

        獲取國(guó)際化消息

        最后總結(jié),全局異常屬于老生長(zhǎng)談的話(huà)題,希望這次通過(guò)手機(jī)的項(xiàng)目對(duì)大家有點(diǎn)指導(dǎo)性的學(xué)習(xí)。大家根據(jù)實(shí)際情況自行修改。

        也可以采用以下的jsonResult對(duì)象的方式進(jìn)行處理,也貼出來(lái)代碼.

        @Slf4j
        @RestControllerAdvice
        public?class?GlobalExceptionHandler?{

        ????/**
        ?????*?沒(méi)有登錄
        ?????*?@param?request
        ?????*?@param?response
        ?????*?@param?e
        ?????*?@return
        ?????*/

        ????@ExceptionHandler(NoLoginException.class)
        ????public?Object?noLoginExceptionHandler(HttpServletRequest?request,HttpServletResponse?response,Exception?e)?
        {
        ????????log.error("[GlobalExceptionHandler][noLoginExceptionHandler]?exception",e);
        ????????JsonResult?jsonResult?=?new?JsonResult();
        ????????jsonResult.setCode(JsonResultCode.NO_LOGIN);
        ????????jsonResult.setMessage("用戶(hù)登錄失效或者登錄超時(shí),請(qǐng)先登錄");
        ????????return?jsonResult;
        ????}

        ????/**
        ?????*?業(yè)務(wù)異常
        ?????*?@param?request
        ?????*?@param?response
        ?????*?@param?e
        ?????*?@return
        ?????*/

        ????@ExceptionHandler(ServiceException.class)
        ????public?Object?businessExceptionHandler(HttpServletRequest?request,HttpServletResponse?response,Exception?e)?
        {
        ????????log.error("[GlobalExceptionHandler][businessExceptionHandler]?exception",e);
        ????????JsonResult?jsonResult?=?new?JsonResult();
        ????????jsonResult.setCode(JsonResultCode.FAILURE);
        ????????jsonResult.setMessage("業(yè)務(wù)異常,請(qǐng)聯(lián)系管理員");
        ????????return?jsonResult;
        ????}

        ????/**
        ?????*?全局異常處理
        ?????*?@param?request
        ?????*?@param?response
        ?????*?@param?e
        ?????*?@return
        ?????*/

        ????@ExceptionHandler(Exception.class)
        ????public?Object?exceptionHandler(HttpServletRequest?request,HttpServletResponse?response,Exception?e)?
        {
        ????????log.error("[GlobalExceptionHandler][exceptionHandler]?exception",e);
        ????????JsonResult?jsonResult?=?new?JsonResult();
        ????????jsonResult.setCode(JsonResultCode.FAILURE);
        ????????jsonResult.setMessage("系統(tǒng)錯(cuò)誤,請(qǐng)聯(lián)系管理員");
        ????????return?jsonResult;
        ????}
        }


        來(lái)源:cnblogs.com/jurendage/p/11255197.html

        程序汪資料鏈接

        程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理

        Java項(xiàng)目分享 ?最新整理全集,找項(xiàng)目不累啦 07版

        堪稱(chēng)神級(jí)的Spring Boot手冊(cè),從基礎(chǔ)入門(mén)到實(shí)戰(zhàn)進(jìn)階

        臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開(kāi)放下載!

        臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開(kāi)放下載!

        字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開(kāi)放下載!


        歡迎添加程序汪個(gè)人微信 itwang009? 進(jìn)粉絲群或圍觀(guān)朋友圈

        瀏覽 40
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产AV一级毛片 | 男生和女生搞鸡视频 | 小舞被肉干高h潮文不断视频 | 女人高潮娇喘呻吟声 | 健身教练被cao的嗷嗷乱叫 | 大肉大捧一进一出视频的注意事项 | 男人添女荫道口喷水 | 亚洲日韩AV在线 | 欧美激情在线 | 青青草自拍视频 |