相信你們在剛開始體驗 Springboot 的時候必定會常常碰到這個頁面,也就是訪問一個不存在的頁面的默認返回頁面。 css
<!-- more -->若是是其餘客戶端請求,如接口測試工具,會默認返回JSON數據。html
{ "timestamp":"2019-01-06 22:26:16", "status":404, "error":"Not Found", "message":"No message available", "path":"/asdad" }
很明顯,SpringBoot 根據 HTTP 的請求頭信息進行了不一樣的響應處理。HTTP 相關知識能夠參考此處。java
追隨 SpringBoot 源碼能夠分析出默認的錯誤處理機制。git
// org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration // 綁定一些錯誤信息 記爲 1 @Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes( this.serverProperties.getError().isIncludeException()); } // 默認處理 /error 記爲 2 @Bean @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) { return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers); } // 錯誤處理頁面 記爲3 @Bean public ErrorPageCustomizer errorPageCustomizer() { return new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath); } @Configuration static class DefaultErrorViewResolverConfiguration { private final ApplicationContext applicationContext; private final ResourceProperties resourceProperties; DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext, ResourceProperties resourceProperties) { this.applicationContext = applicationContext; this.resourceProperties = resourceProperties; } // 決定去哪一個錯誤頁面 記爲4 @Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean public DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties); } }
結合上面的註釋,上面代碼裏的四個方法就是 Springboot 實現默認返回錯誤頁面主要部分。github
errorAttributes
直譯爲錯誤屬性,這個方法確實如此,直接追蹤源代碼。
代碼位於:web
// org.springframework.boot.web.servlet.error.DefaultErrorAttributes
這個類裏爲錯誤狀況共享不少錯誤信息,如。spring
errorAttributes.put("timestamp", new Date()); errorAttributes.put("status", status); errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase()); errorAttributes.put("errors", result.getAllErrors()); errorAttributes.put("exception", error.getClass().getName()); errorAttributes.put("message", error.getMessage()); errorAttributes.put("trace", stackTrace.toString()); errorAttributes.put("path", path);
這些信息用做共享信息返回,因此當咱們使用模版引擎時,也能夠像取出其餘參數同樣輕鬆取出。json
直接追蹤 BasicErrorController
的源碼內容能夠發現下面的一段代碼。bootstrap
// org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController @Controller // 定義請求路徑,若是沒有error.path路徑,則路徑爲/error @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController { // 若是支持的格式 text/html @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); // 獲取要返回的值 Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes( request, isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); // 解析錯誤視圖信息,也就是下面1.4中的邏輯 ModelAndView modelAndView = resolveErrorView(request, response, status, model); // 返回視圖,若是沒有存在的頁面模版,則使用默認錯誤視圖模版 return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { // 若是是接受全部格式的HTTP請求 Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); // 響應HttpEntity return new ResponseEntity<>(body, status); } }
由上可知,basicErrorControll
用於建立用於請求返回的 controller
類,並根據HTTP請求可接受的格式不一樣返回對應的信息,因此在使用瀏覽器和接口測試工具測試時返回結果存在差別。瀏覽器
直接查看方法裏的new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
//org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.ErrorPageCustomizer /** * {@link WebServerFactoryCustomizer} that configures the server's error pages. */ private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered { private final ServerProperties properties; private final DispatcherServletPath dispatcherServletPath; protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) { this.properties = properties; this.dispatcherServletPath = dispatcherServletPath; } // 註冊錯誤頁面 // this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()) @Override public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { //getPath()獲得以下地址,若是沒有自定義error.path屬性,則去/error位置 //@Value("${error.path:/error}") //private String path = "/error"; ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath .getRelativePath(this.properties.getError().getPath())); errorPageRegistry.addErrorPages(errorPage); } @Override public int getOrder() { return 0; } }
由上可知,當遇到錯誤時,若是沒有自定義 error.path
屬性,則請求轉發至 /error
.
根據上面的代碼,一步步深刻查看 SpringBoot 的默認錯誤處理實現,查看看 conventionErrorViewResolver
方法。下面是 DefaultErrorViewResolver 類的部分代碼,註釋解析。
// org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver // 初始化參數,key 是HTTP狀態碼第一位。 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); } @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { // 使用HTTP完整狀態碼檢查是否有頁面能夠匹配 ModelAndView modelAndView = resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { // 使用 HTTP 狀態碼第一位匹配初始化中的參數建立視圖對象 modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { // 拼接錯誤視圖路徑 /eroor/[viewname] String errorViewName = "error/" + viewName; // 使用模版引擎嘗試建立視圖對象 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders .getProvider(errorViewName, this.applicationContext); if (provider != null) { return new ModelAndView(errorViewName, model); } // 沒有模版引擎,使用靜態資源文件夾解析視圖 return resolveResource(errorViewName, model); } private ModelAndView resolveResource(String viewName, Map<String, Object> model) { // 遍歷靜態資源文件夾,檢查是否有存在視圖 for (String location : this.resourceProperties.getStaticLocations()) { try { Resource resource = this.applicationContext.getResource(location); resource = resource.createRelative(viewName + ".html"); if (resource.exists()) { return new ModelAndView(new HtmlResourceView(resource), model); } } catch (Exception ex) { } } return null; }
而 Thymeleaf 對於錯誤頁面的解析實現。
//org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider public class ThymeleafTemplateAvailabilityProvider implements TemplateAvailabilityProvider { @Override public boolean isTemplateAvailable(String view, Environment environment, ClassLoader classLoader, ResourceLoader resourceLoader) { if (ClassUtils.isPresent("org.thymeleaf.spring5.SpringTemplateEngine", classLoader)) { String prefix = environment.getProperty("spring.thymeleaf.prefix", ThymeleafProperties.DEFAULT_PREFIX); String suffix = environment.getProperty("spring.thymeleaf.suffix", ThymeleafProperties.DEFAULT_SUFFIX); return resourceLoader.getResource(prefix + view + suffix).exists(); } return false; } }
從而咱們能夠得知,錯誤頁面首先會檢查模版引擎
文件夾下的 /error/HTTP狀態碼
文件,若是不存在,則檢查去模版引擎下的/error/4xx
或者 /error/5xx
文件,若是還不存在,則檢查靜態資源
文件夾下對應的上述文件。
通過上面的 SpringBoot 錯誤機制源碼分析,知道當遇到錯誤狀況時候,SpringBoot 會首先返回到模版引擎
文件夾下的 /error/HTTP
狀態碼 文件,若是不存在,則檢查去模版引擎下的/error/4xx
或者 /error/5xx
文件,若是還不存在,則檢查靜態資源
文件夾下對應的上述文件。而且在返回時會共享一些錯誤信息,這些錯誤信息能夠在模版引擎中直接使用。
errorAttributes.put("timestamp", new Date()); errorAttributes.put("status", status); errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase()); errorAttributes.put("errors", result.getAllErrors()); errorAttributes.put("exception", error.getClass().getName()); errorAttributes.put("message", error.getMessage()); errorAttributes.put("trace", stackTrace.toString()); errorAttributes.put("path", path);
所以,須要自定義錯誤頁面,只須要在模版文件夾下的 error 文件夾下防止4xx 或者 5xx 文件便可。
<!doctype html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>[[${status}]]</title> <!-- Bootstrap core CSS --> <link href="/webjars/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"> </head> <body > <div class="m-5" > <p>錯誤碼:[[${status}]]</p> <p >信息:[[${message}]]</p> <p >時間:[[${#dates.format(timestamp,'yyyy-MM-dd hh:mm:ss ')}]]</p> <p >請求路徑:[[${path}]]</p> </div> </body> </html>
隨意訪問不存在路徑獲得。
根據上面的 SpringBoot 錯誤處理原理分析,得知最終返回的 JSON 信息是從一個 map 對象中轉換出來的,那麼,只要能自定義 map 中的值,就能夠自定義錯誤信息的 json 格式了。直接重寫 DefaultErrorAttributes
類的 getErrorAttributes
方法便可。
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; import org.springframework.stereotype.Component; import org.springframework.web.context.request.WebRequest; import java.util.HashMap; import java.util.Map; /** * <p> * 自定義錯誤信息JSON值 * * @Author niujinpeng * @Date 2019/1/7 15:21 */ @Component public class ErrorAttributesCustom extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace); String code = map.get("status").toString(); String message = map.get("error").toString(); HashMap<String, Object> hashMap = new HashMap<>(); hashMap.put("code", code); hashMap.put("message", message); return hashMap; } }
使用 postman 請求測試。
使用 @ControllerAdvice
結合@ExceptionHandler
註解能夠實現統一的異常處理,@ExceptionHandler
註解的類會自動應用在每個被 @RequestMapping
註解的方法。當程序中出現異常時會層層上拋
import lombok.extern.slf4j.Slf4j; import net.codingme.boot.domain.Response; import net.codingme.boot.enums.ResponseEnum; import net.codingme.boot.utils.ResponseUtill; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; /** * <p> * 統一的異常處理 * * @Author niujinpeng * @Date 2019/1/7 14:26 */ @Slf4j @ControllerAdvice public class ExceptionHandle { @ResponseBody @ExceptionHandler(Exception.class) public Response handleException(Exception e) { log.info("異常 {}", e); if (e instanceof BaseException) { BaseException exception = (BaseException) e; String code = exception.getCode(); String message = exception.getMessage(); return ResponseUtill.error(code, message); } return ResponseUtill.error(ResponseEnum.UNKNOW_ERROR); } }
請求異常頁面獲得響應以下。
{ "code": "-1", "data": [], "message": "未知錯誤" }
文章代碼已經上傳到 GitHub Spring Boot Web開發 - 錯誤機制。
<完> 歡迎點贊關注! 本文原發於我的博客:https://www.codingme.net 轉載請註明出處