SpringBoot中的全局異常處理
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質(zhì)文章,第一時間送達
? 作者?|??天喬巴夏丶
來源 |? urlify.cn/yIRB7v
本篇要點
介紹SpringBoot默認的異常處理機制。
如何定義錯誤頁面。
如何自定義異常數(shù)據(jù)。
如何自定義視圖解析。
介紹@ControllerAdvice注解處理異常。
一、SpringBoot默認的異常處理機制
默認情況下,SpringBoot為以下兩種情況提供了不同的響應(yīng)方式:
Browser Clients瀏覽器客戶端:通常情況下請求頭中的Accept會包含text/html,如果未定義/error的請求處理,就會出現(xiàn)如下html頁面:Whitelabel Error Page,關(guān)于error頁面的定制,接下來會詳細介紹。

Machine Clients機器客戶端:Ajax請求,返回ResponseEntity實體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默認提供了程序出錯的結(jié)果映射路徑/error,這個請求的處理邏輯在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對象,返回頁面
????ModelAndView?modelAndView?=?resolveErrorView(request,?response,?status,?model);
????return?(modelAndView?!=?null)???modelAndView?:?new?ModelAndView("error",?model);
}
@RequestMapping
public?ResponseEntity 二、錯誤頁面的定制
相信Whitelabel Error Pag頁面我們經(jīng)常會遇到,這樣體驗不是很好,在SpringBoot中可以嘗試定制錯誤頁面,定制方式主要分靜態(tài)和動態(tài)兩種:
靜態(tài)異常頁面
在classpath:/public/error或classpath:/static/error路徑下定義相關(guān)頁面:文件名應(yīng)為確切的狀態(tài)代碼,如404.html,或系列掩碼,如4xx.html。
舉個例子,如果你想匹配404或5開頭的所有狀態(tài)代碼到靜態(tài)的html文件,你的文件目錄應(yīng)該是下面這個樣子:
src/
?+-?main/
?????+-?java/
?????|???+?<source?code>
?????+-?resources/
?????????+-?public/
?????????????+-?error/
?????????????|???+-?404.html
???????|???+-?5xx.html
?????????????+-?
如果500.html和5xx.html同時生效,那么優(yōu)先展示500.html頁面。
使用模板的動態(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)頁面和動態(tài)頁面同時存在且都能匹配,SpringBoot對于錯誤頁面的優(yōu)先展示規(guī)則如下:
如果發(fā)生了500錯誤:
動態(tài)500 -> 靜態(tài)500 -> 動態(tài)5xx -> 靜態(tài)5xx
三、自定義異常數(shù)據(jù)
默認的數(shù)據(jù)主要是以下幾個,這些數(shù)據(jù)定義在org.springframework.boot.web.servlet.error.DefaultErrorAttributes中,數(shù)據(jù)的定義在getErrorAttributes方法中。

DefaultErrorAttributes類在org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration自動配置類中定義:
?@Bean
?@ConditionalOnMissingBean(value?=?ErrorAttributes.class,
??????????????????????????????search?=?SearchStrategy.CURRENT)
?public?DefaultErrorAttributes?errorAttributes()?{
??return?new?DefaultErrorAttributes();
?}
如果我們沒有提供ErrorAttributes的實例,SpringBoot默認提供一個DefaultErrorAttributes實例。
因此,我們就該知道如何去自定義異常數(shù)據(jù)屬性:
實現(xiàn)ErrorAttributes接口。
繼承DefaultErrorAttributes,本身已經(jīng)定義對異常數(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方法中,用于返回一個ModelAndView對象,這個方法中,首先通過getErrorAttributes獲取到異常數(shù)據(jù),然后調(diào)用resolveErrorView去創(chuàng)建一個ModelAndView對象,只有創(chuàng)建失敗的時候,用戶才會看到默認的錯誤提示頁面。
@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ù)相同,如果我們定義了一個ErrorViewResolver的實例,默認的配置就會失效。
/**
?*?自定義異常視圖解析
?*?@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);
????}
}
此時,SpringBoot將會去/hyh目錄下尋找resolve.html頁面。
五、@ControllerAdvice注解處理異常
前后端分離的年代,后端往往需要向前端返回統(tǒng)一格式的json信息,以下為封裝的AjaxResult對象:
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ù)對象
????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ù)的需求不同,我們往往也需要自定義異常類,便于維護:
/**
?*?自定義異常
?*
?*?@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指定需要處理的異常類,針對自定義異常,如果是ajax請求,返回json信息,如果是普通web請求,返回ModelAndView對象。
/**
?*?全局異常處理器
?*?@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定義的動態(tài)模板頁面展示數(shù)據(jù):
"en"?xmlns:th="http://www.thymeleaf.org">
????
????????"UTF-8">
????????自定義界面
????
????
????????"'msg?-->'+?${msg}">
????????"'code?-->'+?${code}">
????????"'url?-->'+?${url}">
????
源碼下載
本文內(nèi)容均為對優(yōu)秀博客及官方文檔總結(jié)而得,原文地址均已在文中參考閱讀處標注。最后,文中的代碼樣例已經(jīng)全部上傳至Gitee:https://gitee.com/tqbx/springboot-samples-learn
參考閱讀
Spring Boot干貨系列:(十三)Spring Boot全局異常處理整理
Springboot:Error-handling
江南一點雨:Spring Boot 中關(guān)于自定義異常處理的套路!
粉絲福利:實戰(zhàn)springboot+CAS單點登錄系統(tǒng)視頻教程免費領(lǐng)取
???
?長按上方微信二維碼?2 秒 即可獲取資料
感謝點贊支持下哈?
