<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 20972字,需瀏覽 42分鐘

           ·

          2022-03-18 23:10

          來源:cnblogs.com/jurendage/p/11255197.html

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

          背景

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

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

          丑陋的 try catch 代碼塊

          優(yōu)雅的Controller

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          目標(biāo)

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

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

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

          用 Assert(斷言) 替換 throw exception

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

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

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

          有沒有感覺第一種判定非空的寫法很優(yōu)雅,第二種寫法則是相對丑陋的?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 {...}?封裝了一下,是不是很神奇。雖然很簡單,但不可否認(rèn)的是編碼體驗(yàn)至少提升了一個(gè)檔次。那么我們能不能模仿org.springframework.util.Assert,也寫一個(gè)斷言類,不過斷言失敗后拋出的異常不是IllegalArgumentException?這些內(nè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);

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

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

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

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

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

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

          因?yàn)闃I(yè)務(wù)邏輯中出現(xiàn)的異常基本都是對應(yīng)特定的場景,比如根據(jù)用戶id獲取用戶信息,查詢結(jié)果為null,此時(shí)拋出的異常可能為UserNotFoundException,并且有特定的異常碼(比如7001)和異常信息“用戶不存在”。所以具體拋出什么異常,有Assert的實(shí)現(xiàn)類決定。

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

          善解人意的Enum

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

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

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


          ?*?

          業(yè)務(wù)處理時(shí),出現(xiàn)異常,可以拋出該異常


          ?*/

          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;
          }

          看到這里,有沒有眼前一亮的感覺,代碼示例中定義了兩個(gè)枚舉實(shí)例:BAD_LICENCE_TYPELICENCE_NOT_FOUND,分別對應(yīng)了BadLicenceTypeExceptionLicenceNotFoundException兩種異常。

          以后每增加一種異常情況,只需增加一個(gè)枚舉實(shí)例即可,再也不用每一種異常都定義一個(gè)異常類了。然后再來看下如何使用,假設(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.");
          ????????}
          ????}

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

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

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

          @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;

          ????/**
          ?????*?獲取國際化消息
          ?????*
          ?????*?@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)境,?不適合把具體的異常信息展示給用戶,?比如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)境,?不適合把具體的異常信息展示給用戶,?比如數(shù)據(jù)庫異常信息.
          ????????????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());
          ????}

          }

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

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

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

          異常處理器說明

          handleServletException

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

          • NoHandlerFoundException

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

          • HttpRequestMethodNotSupportedException

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

          • HttpMediaTypeNotSupportedException

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

          • MissingPathVariableException

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

          • MissingServletRequestParameterException

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

          • TypeMismatchException

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

          • HttpMessageNotReadableException

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

          • HttpMessageNotWritableException

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

          handleBindException

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

          handleValidException

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

          handleBusinessException、handleBaseException

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

          handleException

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

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

          異于常人的404

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

          Whitelabel Error Page

          這個(gè)頁面是如何出現(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頁面即可

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

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

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

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

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

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

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

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

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

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

          因?yàn)檫@一套統(tǒng)一異常處理可以說是通用的,所有可以設(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;

          ????/**
          ?????*?查詢{@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);
          ????}

          ????/**
          ?????*?分頁獲取
          ?????*?@param?licenceParam?分頁查詢參數(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?請求體
          ?????*?@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');
          開始驗(yàn)證
          捕獲自定義異常
          1. 獲取不存在的?licence?詳情:http://localhost:10000/licence/5。成功響應(yīng)的請求:licenceId=1

            檢驗(yàn)非空

            捕獲 Licence not found 異常

            Licence not found

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

            校驗(yàn)非空

            捕獲 Bad licence type 異常

            Bad licence type

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

          1. 訪問不存在的接口: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 請求,這里使用postman模擬。

            addLicence

            LicenceAddRequest

            請求url即結(jié)果

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

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

            異常信息的收集

          捕獲未知異常

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

          增加test字段

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

          Error querying database

          小結(jié)

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

          擴(kuò)展

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

          生產(chǎn)環(huán)境返回“網(wǎng)絡(luò)異常”

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

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

          總結(jié)

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

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

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

          獲取國際化消息

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

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

          @Slf4j
          @RestControllerAdvice
          public?class?GlobalExceptionHandler?{

          ????/**
          ?????*?沒有登錄
          ?????*?@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("用戶登錄失效或者登錄超時(shí),請先登錄");
          ????????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ù)異常,請聯(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ò)誤,請聯(lián)系管理員");
          ????????return?jsonResult;
          ????}
          }


          來源:cnblogs.com/jurendage/p/11255197.html

          程序汪資料鏈接

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

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

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

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

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

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


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

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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青娱乐一区二区三区 | 夜色视频在线免费观看 | 午夜网址 | 丁香五月骚婷 | 亚洲天堂在线播放 |