javascript
SpringCloud:统一异常处理
程序員缺乏經驗的 7 種表現
2021年4月程序員工資統計:平均14596元,南京程序員收入擠進一線。
作者:BNDong
www.cnblogs.com/bndong/p/10135370.html
在啟動應用時會發現在控制臺打印的日志中出現了兩個路徑為 {[/error]} 的訪問地址,當系統中發送異常錯誤時,Spring Boot 會根據請求方式分別跳轉到以 JSON 格式或以界面顯示的 /error 地址中顯示錯誤信息。
默認異常處理
使用 AJAX 方式請求時返回的 JSON 格式錯誤信息。
{"timestamp":?"2018-12-18T01:50:51.196+0000","status":?404,"error":?"Not?Found","message":?"No?handler?found?for?GET?/err404","path":?"/err404" }使用瀏覽器請求時返回的錯誤信息界面。
自定義異常處理
引入依賴
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.54</version> </dependency> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId> </dependency>fastjson 是 JSON 序列化依賴, spring-boot-starter-freemarker 是一個模板引擎,用于我們設置錯誤輸出模板。
增加配置
properties
#?出現錯誤時,?直接拋出異常(便于異常統一處理,否則捕獲不到404) spring.mvc.throw-exception-if-no-handler-found=true #?不要為工程中的資源文件建立映射 spring.resources.add-mappings=falseyml
spring:#?出現錯誤時,?直接拋出異常(便于異常統一處理,否則捕獲不到404)mvc:throw-exception-if-no-handler-found:?true#?不要為工程中的資源文件建立映射resources:add-mappings:?false新建錯誤信息實體
/***?信息實體*/ public?class?ExceptionEntity?implements?Serializable?{private?static?final?long?serialVersionUID?=?1L;private?String?message;private?int????code;private?String?error;private?String?path;@JSONField(format?=?"yyyy-MM-dd?hh:mm:ss")private?Date?timestamp?=?new?Date();public?static?long?getSerialVersionUID()?{return?serialVersionUID;}public?String?getMessage()?{return?message;}public?void?setMessage(String?message)?{this.message?=?message;}public?int?getCode()?{return?code;}public?void?setCode(int?code)?{this.code?=?code;}public?String?getError()?{return?error;}public?void?setError(String?error)?{this.error?=?error;}public?String?getPath()?{return?path;}public?void?setPath(String?path)?{this.path?=?path;}public?Date?getTimestamp()?{return?timestamp;}public?void?setTimestamp(Date?timestamp)?{this.timestamp?=?timestamp;} }新建自定義異常
/***?自定義異常*/ public?class?BasicException?extends?RuntimeException?{private?static?final?long?serialVersionUID?=?1L;private?int?code?=?0;public?BasicException(int?code,?String?message)?{super(message);this.code?=?code;}public?int?getCode()?{return?this.code;} } /***?業務異常*/ public?class?BusinessException?extends?BasicException?{private?static?final?long?serialVersionUID?=?1L;public?BusinessException(int?code,?String?message)?{super(code,?message);} }BasicException 繼承了 RuntimeException ,并在原有的 Message 基礎上增加了錯誤碼 code 的內容。而 BusinessException 則是在業務中具體使用的自定義異常類,起到了對不同的異常信息進行分類的作用。
新建 error.ftl 模板文件
位置:/src/main/resources/templates/ 用于顯示錯誤信息
<!DOCTYPE?html> <html> <head><meta?name="robots"?content="noindex,nofollow"?/><meta?name="viewport"?content="width=device-width,?initial-scale=1,?user-scalable=no"><style>h2{color:?#4288ce;font-weight:?400;padding:?6px?0;margin:?6px?0?0;font-size:?18px;border-bottom:?1px?solid?#eee;}/*?Exception?Variables?*/.exception-var?table{width:?100%;max-width:?500px;margin:?12px?0;box-sizing:?border-box;table-layout:fixed;word-wrap:break-word;}.exception-var?table?caption{text-align:?left;font-size:?16px;font-weight:?bold;padding:?6px?0;}.exception-var?table?caption?small{font-weight:?300;display:?inline-block;margin-left:?10px;color:?#ccc;}.exception-var?table?tbody{font-size:?13px;font-family:?Consolas,"Liberation?Mono",Courier,"微軟雅黑";}.exception-var?table?td{padding:?0?6px;vertical-align:?top;word-break:?break-all;}.exception-var?table?td:first-child{width:?28%;font-weight:?bold;white-space:?nowrap;}.exception-var?table?td?pre{margin:?0;}</style> </head> <body><div?class="exception-var"><h2>Exception?Datas</h2><table><tbody><tr><td>Code</td><td>${(exception.code)!}</td></tr><tr><td>Time</td><td>${(exception.timestamp?datetime)!}</td></tr><tr><td>Path</td><td>${(exception.path)!}</td></tr><tr><td>Exception</td><td>${(exception.error)!}</td></tr><tr><td>Message</td><td>${(exception.message)!}</td></tr></tbody></table> </div> </body> </html>編寫全局異常控制類
/***?全局異常控制類*/ @ControllerAdvice public?class?GlobalExceptionHandler?{/***?404異常處理*/@ExceptionHandler(value?=?NoHandlerFoundException.class)@ResponseStatus(HttpStatus.NOT_FOUND)public?ModelAndView?errorHandler(HttpServletRequest?request,?NoHandlerFoundException?exception,?HttpServletResponse?response)?{return?commonHandler(request,?response,exception.getClass().getSimpleName(),HttpStatus.NOT_FOUND.value(),exception.getMessage());}/***?405異常處理*/@ExceptionHandler(HttpRequestMethodNotSupportedException.class)public?ModelAndView?errorHandler(HttpServletRequest?request,?HttpRequestMethodNotSupportedException?exception,?HttpServletResponse?response)?{return?commonHandler(request,?response,exception.getClass().getSimpleName(),HttpStatus.METHOD_NOT_ALLOWED.value(),exception.getMessage());}/***?415異常處理*/@ExceptionHandler(HttpMediaTypeNotSupportedException.class)public?ModelAndView?errorHandler(HttpServletRequest?request,?HttpMediaTypeNotSupportedException?exception,?HttpServletResponse?response)?{return?commonHandler(request,?response,exception.getClass().getSimpleName(),HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(),exception.getMessage());}/***?500異常處理*/@ExceptionHandler(value?=?Exception.class)public?ModelAndView?errorHandler?(HttpServletRequest?request,?Exception?exception,?HttpServletResponse?response)?{return?commonHandler(request,?response,exception.getClass().getSimpleName(),HttpStatus.INTERNAL_SERVER_ERROR.value(),exception.getMessage());}/***?業務異常處理*/@ExceptionHandler(value?=?BasicException.class)private?ModelAndView?errorHandler?(HttpServletRequest?request,?BasicException?exception,?HttpServletResponse?response)?{return?commonHandler(request,?response,exception.getClass().getSimpleName(),exception.getCode(),exception.getMessage());}/***?表單驗證異常處理*/@ExceptionHandler(value?=?BindException.class)@ResponseBodypublic?ExceptionEntity?validExceptionHandler(BindException?exception,?HttpServletRequest?request,?HttpServletResponse?response)?{List<FieldError>?fieldErrors?=?exception.getBindingResult().getFieldErrors();Map<String,String>?errors?=?new?HashMap<>();for?(FieldError?error:fieldErrors)?{errors.put(error.getField(),?error.getDefaultMessage());}ExceptionEntity?entity?=?new?ExceptionEntity();entity.setMessage(JSON.toJSONString(errors));entity.setPath(request.getRequestURI());entity.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());entity.setError(exception.getClass().getSimpleName());response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());return?entity;}/***?異常處理數據處理*/private?ModelAndView?commonHandler?(HttpServletRequest?request,?HttpServletResponse?response,String?error,?int?httpCode,?String?message)?{ExceptionEntity?entity?=?new?ExceptionEntity();entity.setPath(request.getRequestURI());entity.setError(error);entity.setCode(httpCode);entity.setMessage(message);return?determineOutput(request,?response,?entity);}/***?異常輸出處理*/private?ModelAndView?determineOutput(HttpServletRequest?request,?HttpServletResponse?response,?ExceptionEntity?entity)?{if?(!(request.getHeader("accept").contains("application/json")||?(request.getHeader("X-Requested-With")?!=?null?&&?request.getHeader("X-Requested-With").contains("XMLHttpRequest"))))?{ModelAndView?modelAndView?=?new?ModelAndView("error");modelAndView.addObject("exception",?entity);return?modelAndView;}?else?{response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());response.setCharacterEncoding("UTF8");response.setHeader("Content-Type",?"application/json");try?{response.getWriter().write(ResultJsonTools.build(ResponseCodeConstant.SYSTEM_ERROR,ResponseMessageConstant.APP_EXCEPTION,JSONObject.parseObject(JSON.toJSONString(entity))));}?catch?(IOException?e)?{e.printStackTrace();}return?null;}} }@ControllerAdvice
作用于類上,用于標識該類用于處理全局異常。
@ExceptionHandler
作用于方法上,用于對攔截的異常類型進行處理。value 屬性用于指定具體的攔截異常類型,如果有多個 ExceptionHandler 存在,則需要指定不同的 value 類型,由于異常類擁有繼承關系,所以 ExceptionHandler 會首先執行在繼承樹中靠前的異常類型。
BindException
該異常來自于表單驗證框架 Hibernate validation,當字段驗證未通過時會拋出此異常。
編寫測試 Controller
@RestController public?class?TestController?{@RequestMapping(value?=?"err")public?void?error(){throw?new?BusinessException(400,?"業務異常錯誤信息");}@RequestMapping(value?=?"err2")public?void?error2(){throw?new?NullPointerException("手動拋出異常信息");}@RequestMapping(value?=?"err3")public?int?error3(){int?a?=?10?/?0;return?a;} }使用 AJAX 方式請求時返回的 JSON 格式錯誤信息。
#?/err {"msg":?"應用程序異常","code":?-1,"status_code":?0,"data":?{"path":?"/err","code":?400,"error":?"BusinessException","message":?"業務異常錯誤信息","timestamp":?"2018-12-18?11:09:00"} }#?/err2 {"msg":?"應用程序異常","code":?-1,"status_code":?0,"data":?{"path":?"/err2","code":?500,"error":?"NullPointerException","message":?"手動拋出異常信息","timestamp":?"2018-12-18?11:15:15"} }#?/err3 {"msg":?"應用程序異常","code":?-1,"status_code":?0,"data":?{"path":?"/err3","code":?500,"error":?"ArithmeticException","message":?"/?by?zero","timestamp":?"2018-12-18?11:15:46"} }#?/err404 {"msg":?"應用程序異常","code":?-1,"status_code":?0,"data":?{"path":?"/err404","code":?404,"error":?"NoHandlerFoundException","message":?"No?handler?found?for?GET?/err404","timestamp":?"2018-12-18?11:16:11"} }使用瀏覽器請求時返回的錯誤信息界面。
示例代碼:https://github.com/BNDong/spring-cloud-examples/tree/master/spring-cloud-zuul/cloud-zuul
參考資料
《微服務 分布式架構開發實戰》 龔鵬 著
https://www.jianshu.com/p/1a49fa436623
推薦文章2021年4月程序員工資統計:平均14596元,南京程序員收入擠進一線。
常見的SQL面試題:經典50例
47K Star 的SpringBoot+MyBatis+docker電商項目,附帶超詳細的文檔!
寫博客能月入10K?
一款基于 Spring Boot 的現代化社區(論壇/問答/社交網絡/博客)
這或許是最美的Vue+Element開源后臺管理UI
推薦一款高顏值的 Spring Boot 快速開發框架
一款基于 Spring Boot 的現代化社區(論壇/問答/社交網絡/博客)
13K點贊都基于 Vue+Spring 前后端分離管理系統ELAdmin,大愛
想接私活時薪再翻一倍,建議根據這幾個開源的SpringBoot
總結
以上是生活随笔為你收集整理的SpringCloud:统一异常处理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Nginx高效学习手册(建议收藏)
- 下一篇: 16 个写代码的好习惯