前面一片已經介紹了springboot錯誤處理的機制,其實從整個分析過程中我們已經大概知道如何定制了。
1、錯誤頁面自定義
springboot有個默認的錯誤頁面,但是開發時錯誤頁面肯定是自己定義的。那該如何定義?
在DefaultErrorViewResolver類中有下面幾個方法,
private ModelAndView resolve(String viewName, Map<String, Object> model) { // 定義視圖名,這里我們可以確定視圖名:error/錯誤碼,例如:error/404, String errorViewName = "error/" + viewName; // 這里結合上面的errorViewName,其實就是在template目錄下的error目錄進行查找 // 我們默認情況下是沒有error目錄,這里的provide最終值為null,代碼較多就不一一展示,有興趣的可以跟下去 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); // 根據判定,這里會接著調用下面的resolveResource方法 return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model); } private ModelAndView resolveResource(String viewName, Map<String, Object> model) { // getStaticLocations()獲取的是靜態資源路徑:"classpath:/META-INF/resources/", "classpath:/resources/","classpath:/static/", "classpath:/public/" String[] var3 = this.resourceProperties.getStaticLocations(); int var4 = var3.length; // 遍歷上面的4個靜態資源路徑 for(int var5 = 0; var5 < var4; ++var5) { String location = var3[var5]; try { Resource resource = this.applicationContext.getResource(location); // 創建resource對象,例如error/404.html resource = resource.createRelative(viewName + ".html"); // 查找在對應靜態資源目錄下是否有上面的這個資源對象,有就創建視圖對象 if (resource.exists()) { return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model); } } catch (Exception var8) { ; } } // 都沒找到就返回null,默認情況下是不存在error目錄的,所以這里最終返回null return null; }
在解析錯誤視圖界面時,會依次去這幾個目錄:template/、 classpath:/META-INF/resources/ 、 classpath:/resources/、 classpath:/static/ 、classpath:/public/,在這些目錄下的error目錄里找文件名是錯誤狀態碼的頁面文件(404、500…)。
所以我們可以在這些目錄下創建error目錄,在里面創建HTML頁面,但是在template下面的頁面才能被thymeleaf模板引擎識別。
現在再去訪問不存在路徑時效果如下,因為是404錯誤
但是,錯誤狀態碼是很多的,不可能將所有的頁面全部寫出來,在DefaultErrorViewResolver類中有下面一段靜態代碼:
static { Map<Series, String> views = new EnumMap(Series.class); views.put(Series.CLIENT_ERROR, "4xx"); views.put(Series.SERVER_ERROR, "5xx"); SERIES_VIEWS = Collections.unmodifiableMap(views); }
向map中添加了兩個數據"4xx"、“5xx”,這個表示4開頭或者5開頭的錯誤,并且看到下面的:
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { // 按狀態碼精確查找 ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model); // 若精確查找未找到再來模糊查找 // status.series()方法可以自己看一下,就是在獲取錯誤碼的首位 if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model); } return modelAndView; }
上面注釋寫的很清楚了,springboot首先按錯誤精確查找,若為找到再以錯誤碼首位模糊查找,所以我們也可以將錯誤頁面文件定義成 4xx.html 或者 5xx.html。
2、錯誤數據
2.1 默認錯誤數據
頁面搞定了,那默認的錯誤數據有哪些呢?響應瀏覽器或者移動端錯誤請求的兩個方法中都同時用到了getErrorAttributes方法,這個方法就是用來獲取錯誤數據的。
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = this.getStatus(request); Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = this.resolveErrorView(request, response, status, model); return modelAndView != null ? modelAndView : new ModelAndView("error", model); } @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = this.getStatus(request); return new ResponseEntity(body, status); }
這個方法是屬于DefaultErrorAttributes類的,用來定義默認錯誤屬性。
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap(); errorAttributes.put("timestamp", new Date()); this.addStatus(errorAttributes, webRequest); this.addErrorDetails(errorAttributes, webRequest, includeStackTrace); this.addPath(errorAttributes, webRequest); return errorAttributes; }
從這個方法及其里面調用其他幾個方法跟蹤可以知道有如下錯誤屬性:
- timestamp:時間戳
- status:錯誤碼
- error:錯誤名
- exception:異常類型
- message:異常信息
- trace:錯誤棧
- path:錯誤路徑
2.2 自定義錯誤數據
下面模擬一個場景,發起一個請求,然后觸發一個自定義異常,自己添加一些錯誤信息,在頁面上顯示出來。 exception:
public class MyException extends RuntimeException { public MyException() { super("發生異常了"); } }
Controller:
@ResponseBody @RequestMapping("/hello") public String hello(@RequestParam("username") String username){ // 當username值為aaa是拋出異常 if(username.equals("aaa")){ throw new MyException(); } return "hello"; }
為了處理異常還要定義個異常處理器,關鍵點1:注意設置狀態碼的注釋:
@ControllerAdvice public class ExceptionController { @ExceptionHandler(MyException.class) public String f(HttpServletRequest request){ // 這里要更改狀態碼,前面訪問路徑是沒有問題的,所以狀態碼為200 // 若想要進入springboot錯誤處理流程,必須重設狀態碼 // 并且其key值為:javax.servlet.error.status_code request.setAttribute("javax.servlet.error.status_code",500); // 這里可以添加信息到request中,到后面的取出添加到map中 request.setAttribute("data","我的錯誤消息"); // 轉發到 /error請求,交給springboot處理 return "forward:/error"; } }
關鍵點2,因為我們的錯誤信都是在DefaultErrorAttributes類中的getErrorAttributes方法中獲取的,若只是到上面步驟為止,那么在移動端將無法獲取到添加的data,所以為止同時使用瀏覽器和移動端,我們還必須創建一個類繼承DefaultErrorAttributes類,重寫getErrorAttributes方法,在這里才是真正的添加自定義的數據。
MyErrorAttribute:
// 注意,給類必須添加到容器中,否則不生效 // 添加后將會覆蓋原有的DefaultErrorAttributes,采用我們自己的MyErrorAtribute @Component public class MyErrorAtribute extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { // 獲取包含錯誤信息的map Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace); // 添加自己的錯誤數據 map.put("company","lr"); //獲取轉發過來時添加的數據 // 第二個參數表示在哪個域中獲取,0:request,1:session String data = (String)webRequest.getAttribute("data", 0); map.put("data",data); // 添加到map return map; } }
然后我們測試,瀏覽器端:
移動端:
這樣,我們錯誤頁面,錯誤消息自定義,瀏覽器和移動端適配都解決了。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/qq_34975710/article/details/90060014