1. SpringBoot中的全局異常處理

        共 9133字,需瀏覽 19分鐘

         ·

        2020-11-08 21:18

        點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

        優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

        ? 作者?|??天喬巴夏丶

        來源 |? urlify.cn/yIRB7v

        66套java從入門到精通實(shí)戰(zhàn)課程分享

        本篇要點(diǎn)

        • 介紹SpringBoot默認(rèn)的異常處理機(jī)制。

        • 如何定義錯(cuò)誤頁面。

        • 如何自定義異常數(shù)據(jù)。

        • 如何自定義視圖解析。

        • 介紹@ControllerAdvice注解處理異常。

        一、SpringBoot默認(rèn)的異常處理機(jī)制

        默認(rèn)情況下,SpringBoot為以下兩種情況提供了不同的響應(yīng)方式:

        1. Browser Clients瀏覽器客戶端:通常情況下請求頭中的Accept會(huì)包含text/html,如果未定義/error的請求處理,就會(huì)出現(xiàn)如下html頁面:Whitelabel Error Page,關(guān)于error頁面的定制,接下來會(huì)詳細(xì)介紹。

        1. Machine Clients機(jī)器客戶端:Ajax請求,返回ResponseEntity實(shí)體json字符串信息。

        {
        ????"timestamp":?"2020-10-30T15:01:17.353+00:00",
        ????"status":?500,
        ????"error":?"Internal?Server?Error",
        ????"trace":?"java.lang.ArithmeticException:?/?by?zero...",
        ????"message":?"/?by?zero",
        ????"path":?"/"
        }

        SpringBoot默認(rèn)提供了程序出錯(cuò)的結(jié)果映射路徑/error,這個(gè)請求的處理邏輯在BasicErrorController中處理,處理邏輯如下:

        //?判斷mediaType類型是否為text/html
        @RequestMapping(produces?=?MediaType.TEXT_HTML_VALUE)
        public?ModelAndView?errorHtml(HttpServletRequest?request,?HttpServletResponse?response)?{
        ????HttpStatus?status?=?getStatus(request);
        ????Map?model?=?Collections
        ????????.unmodifiableMap(getErrorAttributes(request,?getErrorAttributeOptions(request,?MediaType.TEXT_HTML)));
        ????response.setStatus(status.value());
        ????//?創(chuàng)建ModelAndView對(duì)象,返回頁面
        ????ModelAndView?modelAndView?=?resolveErrorView(request,?response,?status,?model);
        ????return?(modelAndView?!=?null)???modelAndView?:?new?ModelAndView("error",?model);
        }

        @RequestMapping
        public?ResponseEntity>?error(HttpServletRequest?request)?{
        ????HttpStatus?status?=?getStatus(request);
        ????if?(status?==?HttpStatus.NO_CONTENT)?{
        ????????return?new?ResponseEntity<>(status);
        ????}
        ????Map?body?=?getErrorAttributes(request,?getErrorAttributeOptions(request,?MediaType.ALL));
        ????return?new?ResponseEntity<>(body,?status);
        }

        二、錯(cuò)誤頁面的定制

        相信Whitelabel Error Pag頁面我們經(jīng)常會(huì)遇到,這樣體驗(yàn)不是很好,在SpringBoot中可以嘗試定制錯(cuò)誤頁面,定制方式主要分靜態(tài)和動(dòng)態(tài)兩種:

        1. 靜態(tài)異常頁面

        classpath:/public/errorclasspath:/static/error路徑下定義相關(guān)頁面:文件名應(yīng)為確切的狀態(tài)代碼,如404.html,或系列掩碼,如4xx.html。

        舉個(gè)例子,如果你想匹配404或5開頭的所有狀態(tài)代碼到靜態(tài)的html文件,你的文件目錄應(yīng)該是下面這個(gè)樣子:

        src/
        ?+-?main/
        ?????+-?java/
        ?????|???+?<source?code>
        ?????+-?resources/
        ?????????+-?public/
        ?????????????+-?error/
        ?????????????|???+-?404.html
        ???????|???+-?5xx.html
        ?????????????+-?

        如果500.html和5xx.html同時(shí)生效,那么優(yōu)先展示500.html頁面。

        1. 使用模板的動(dòng)態(tài)頁面

        放置在classpath:/templates/error路徑下:這里使用thymeleaf模板舉例:

        src/
        ?+-?main/
        ?????+-?java/
        ?????|???+?<source?code>
        ?????+-?resources/
        ?????????+-?templates/
        ?????????????+-?error/
        ?????????????|???+-?5xx.html
        ?????????????+-?

        頁面如下:


        "en"?xmlns:th="http://www.thymeleaf.org">
        ????
        ????????"UTF-8">
        ????????5xx
        ????
        ????
        ????????

        5xx


        ????????"'error?-->'+?${error}">


        ????????"'status?-->'?+?${status}">


        ????????"'timestamp?-->'?+?${timestamp}">


        ????????"'message?-->'?+?${message}">


        ????????"'path?-->'?+${path}">


        ????????"'trace?-->'?+?${trace}">


        ????

        如果靜態(tài)頁面和動(dòng)態(tài)頁面同時(shí)存在且都能匹配,SpringBoot對(duì)于錯(cuò)誤頁面的優(yōu)先展示規(guī)則如下:

        如果發(fā)生了500錯(cuò)誤:

        動(dòng)態(tài)500 -> 靜態(tài)500 -> 動(dòng)態(tài)5xx -> 靜態(tài)5xx

        三、自定義異常數(shù)據(jù)

        默認(rèn)的數(shù)據(jù)主要是以下幾個(gè),這些數(shù)據(jù)定義在org.springframework.boot.web.servlet.error.DefaultErrorAttributes中,數(shù)據(jù)的定義在getErrorAttributes方法中。

        DefaultErrorAttributes類在org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration自動(dòng)配置類中定義:

        ?@Bean
        ?@ConditionalOnMissingBean(value?=?ErrorAttributes.class,
        ??????????????????????????????search?=?SearchStrategy.CURRENT)
        ?public?DefaultErrorAttributes?errorAttributes()?{
        ??return?new?DefaultErrorAttributes();
        ?}

        如果我們沒有提供ErrorAttributes的實(shí)例,SpringBoot默認(rèn)提供一個(gè)DefaultErrorAttributes實(shí)例。

        因此,我們就該知道如何去自定義異常數(shù)據(jù)屬性:

        1. 實(shí)現(xiàn)ErrorAttributes接口。

        2. 繼承DefaultErrorAttributes,本身已經(jīng)定義對(duì)異常數(shù)據(jù)的處理,繼承更具效率。

        定義方式如下:

        /**
        ?*?自定義異常數(shù)據(jù)
        ?*?@author?Summerday
        ?*/
        @Slf4j
        @Component
        public?class?MyErrorAttributes?extends?DefaultErrorAttributes?{

        ????@Override
        ????public?Map?getErrorAttributes(WebRequest?webRequest,?ErrorAttributeOptions?options)?{
        ????????Map?map?=?super.getErrorAttributes(webRequest,?options);
        ????????if(map.get("status").equals(500)){
        ????????????log.warn("服務(wù)器內(nèi)部異常");
        ????????}
        ????????return?map;
        ????}
        }

        四、自定義異常視圖

        自定義視圖的加載邏輯存在于BasicErrorController類的errorHtml方法中,用于返回一個(gè)ModelAndView對(duì)象,這個(gè)方法中,首先通過getErrorAttributes獲取到異常數(shù)據(jù),然后調(diào)用resolveErrorView去創(chuàng)建一個(gè)ModelAndView對(duì)象,只有創(chuàng)建失敗的時(shí)候,用戶才會(huì)看到默認(rèn)的錯(cuò)誤提示頁面。

        @RequestMapping(produces?=?MediaType.TEXT_HTML_VALUE)
        public?ModelAndView?errorHtml(HttpServletRequest?request,?HttpServletResponse?response)?{
        ????HttpStatus?status?=?getStatus(request);
        ????Map?model?=?Collections
        ????????.unmodifiableMap(
        ????????//ErrorAttributes?#?getErrorAttributes
        ????????getErrorAttributes(request,?
        ???????????????????????????getErrorAttributeOptions(request,?MediaType.TEXT_HTML)));
        ????response.setStatus(status.value());
        ????//?E
        ????ModelAndView?modelAndView?=?resolveErrorView(request,?response,?status,?model);
        ????return?(modelAndView?!=?null)???modelAndView?:?new?ModelAndView("error",?model);
        }

        DefaultErrorViewResolver類的resolveErrorView方法:

        @Override
        public?ModelAndView?resolveErrorView(HttpServletRequest?request,?HttpStatus?status,?Map?model)?{
        ????//?以異常狀態(tài)碼作為視圖名去locations路徑中去查找頁面
        ????ModelAndView?modelAndView?=?resolve(String.valueOf(status.value()),?model);
        ????//?如果找不到,以4xx,5xx?series去查找
        ????if?(modelAndView?==?null?&&?SERIES_VIEWS.containsKey(status.series()))?{
        ????????modelAndView?=?resolve(SERIES_VIEWS.get(status.series()),?model);
        ????}
        ????return?modelAndView;
        }

        那么如何自定義呢?和自定義異常數(shù)據(jù)相同,如果我們定義了一個(gè)ErrorViewResolver的實(shí)例,默認(rèn)的配置就會(huì)失效。

        /**
        ?*?自定義異常視圖解析
        ?*?@author?Summerday
        ?*/

        @Component
        public?class?MyErrorViewResolver?extends?DefaultErrorViewResolver?{

        ????public?MyErrorViewResolver(ApplicationContext?applicationContext,?ResourceProperties?resourceProperties)?{
        ????????super(applicationContext,?resourceProperties);
        ????}

        ????@Override
        ????public?ModelAndView?resolveErrorView(HttpServletRequest?request,?HttpStatus?status,?Map?model)?{
        ????????return?new?ModelAndView("/hyh/resolve",model);
        ????}
        }

        此時(shí),SpringBoot將會(huì)去/hyh目錄下尋找resolve.html頁面。

        五、@ControllerAdvice注解處理異常

        前后端分離的年代,后端往往需要向前端返回統(tǒng)一格式的json信息,以下為封裝的AjaxResult對(duì)象:

        public?class?AjaxResult?extends?HashMap?{
        ????
        ????//狀態(tài)碼
        ????public?static?final?String?CODE_TAG?=?"code";
        ????
        ????//返回內(nèi)容
        ????public?static?final?String?MSG_TAG?=?"msg";
        ????
        ????//數(shù)據(jù)對(duì)象
        ????public?static?final?String?DATA_TAG?=?"data";

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

        ????public?AjaxResult()?{
        ????}

        ????public?AjaxResult(int?code,?String?msg)?{
        ????????super.put(CODE_TAG,?code);
        ????????super.put(MSG_TAG,?msg);
        ????}

        ????public?AjaxResult(int?code,?String?msg,?Object?data)?{
        ????????super.put(CODE_TAG,?code);
        ????????super.put(MSG_TAG,?msg);
        ????????if?(data?!=?null)?{
        ????????????super.put(DATA_TAG,?data);
        ????????}
        ????}

        ????public?static?AjaxResult?ok()?{
        ????????return?AjaxResult.ok("操作成功");
        ????}

        ????public?static?AjaxResult?ok(Object?data)?{
        ????????return?AjaxResult.ok("操作成功",?data);
        ????}

        ????public?static?AjaxResult?ok(String?msg)?{
        ????????return?AjaxResult.ok(msg,?null);
        ????}

        ????public?static?AjaxResult?ok(String?msg,?Object?data)?{
        ????????return?new?AjaxResult(HttpStatus.OK.value(),?msg,?data);
        ????}

        ????public?static?AjaxResult?error()?{
        ????????return?AjaxResult.error("操作失敗");
        ????}

        ????public?static?AjaxResult?error(String?msg)?{
        ????????return?AjaxResult.error(msg,?null);
        ????}

        ????public?static?AjaxResult?error(String?msg,?Object?data)?{
        ????????return?new?AjaxResult(HttpStatus.INTERNAL_SERVER_ERROR.value(),?msg,?data);
        ????}

        ????public?static?AjaxResult?error(int?code,?String?msg)?{
        ????????return?new?AjaxResult(code,?msg,?null);
        ????}
        }

        根據(jù)業(yè)務(wù)的需求不同,我們往往也需要自定義異常類,便于維護(hù):

        /**
        ?*?自定義異常
        ?*
        ?*?@author?Summerday
        ?*/
        public?class?CustomException?extends?RuntimeException?{

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

        ????private?Integer?code;

        ????private?final?String?message;

        ????public?CustomException(String?message)?{
        ????????this.message?=?message;
        ????}

        ????public?CustomException(String?message,?Integer?code)?{
        ????????this.message?=?message;
        ????????this.code?=?code;
        ????}

        ????public?CustomException(String?message,?Throwable?e)?{
        ????????super(message,?e);
        ????????this.message?=?message;
        ????}

        ????@Override
        ????public?String?getMessage()?{
        ????????return?message;
        ????}

        ????public?Integer?getCode()?{
        ????????return?code;
        ????}
        }

        @ControllerAdvice和@RestControllerAdvice這倆注解的功能之一,就是做到Controller層面的異常處理,而兩者的區(qū)別,與@Controller和@RestController差不多。

        @ExceptionHandler指定需要處理的異常類,針對(duì)自定義異常,如果是ajax請求,返回json信息,如果是普通web請求,返回ModelAndView對(duì)象。

        /**
        ?*?全局異常處理器
        ?*?@author?Summerday
        ?*/

        @RestControllerAdvice
        public?class?GlobalExceptionHandler?{

        ????private?static?final?Logger?log?=?
        ????????LoggerFactory.getLogger(GlobalExceptionHandler.class);


        ????@ExceptionHandler(CustomException.class)
        ????public?Object?handle(HttpServletRequest?request,?CustomException?e)?{
        ????????AjaxResult?info?=?AjaxResult.error(e.getMessage());
        ????????log.error(e.getMessage());
        ????????//?判斷是否為ajax請求
        ????????if?(isAjaxRequest(request))?{
        ????????????return?info;
        ????????}
        ????????ModelAndView?mv?=?new?ModelAndView();
        ????????mv.setViewName("custom");?//?templates/custom.html
        ????????mv.addAllObjects(info);
        ????????mv.addObject("url",?request.getRequestURL());
        ????????return?mv;
        ????}

        ????private?boolean?isAjaxRequest(HttpServletRequest?request)?{
        ????????return?"XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
        ????}
        }

        在Controller層,人為定義拋出異常:

        @RestController
        public?class?TestController?{

        ????@GetMapping("/ajax")
        ????public?AjaxResult?ajax()?{
        ????????double?alpha?=?0.9;
        ????????if?(Math.random()?????????????throw?new?CustomException("自定義異常!");
        ????????}
        ????????return?AjaxResult.ok();
        ????}
        }

        最后,通過/templates/custom.html定義的動(dòng)態(tài)模板頁面展示數(shù)據(jù):


        "en"?xmlns:th="http://www.thymeleaf.org">
        ????
        ????????"UTF-8">
        ????????自定義界面
        ????
        ????
        ????????"'msg?-->'+?${msg}">


        ????????"'code?-->'+?${code}">


        ????????"'url?-->'+?${url}">


        ????

        源碼下載

        本文內(nèi)容均為對(duì)優(yōu)秀博客及官方文檔總結(jié)而得,原文地址均已在文中參考閱讀處標(biāo)注。最后,文中的代碼樣例已經(jīng)全部上傳至Gitee:https://gitee.com/tqbx/springboot-samples-learn

        參考閱讀

        • Spring Boot干貨系列:(十三)Spring Boot全局異常處理整理

        • Springboot:Error-handling

        • 江南一點(diǎn)雨:Spring Boot 中關(guān)于自定義異常處理的套路!





        粉絲福利:實(shí)戰(zhàn)springboot+CAS單點(diǎn)登錄系統(tǒng)視頻教程免費(fèi)領(lǐng)取

        ???

        ?長按上方微信二維碼?2 秒
        即可獲取資料



        感謝點(diǎn)贊支持下哈?

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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 女生扒开下体让男生桶 | 亚洲精品粉嫩小泬18p图片 | 国产一区二区 | 国产又粗又猛又黄又爽无遮挡 | 任我干在线视频 |