一但系統出現4xx或者5xx之類的錯誤;ErrorPageCustomizer就會生效(定製錯誤的響應規則);就會來到/error請求;就會被BasicErrorController處理;
響應頁面;去哪一個頁面是由DefaultErrorViewResolver解析獲得的;html
ErrorMvcAutoConfiguration ,此類幫咱們自動配置了:java
@Bean public ErrorPageCustomizer errorPageCustomizer() { return new ErrorPageCustomizer(this.serverProperties); } private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered { /*添加錯誤映射,即發生錯誤時會發送 /error 請求*/ @Override public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { ErrorPage errorPage = new ErrorPage( this.properties.getServlet().getServletPrefix() + this.properties.getError().getPath()); errorPageRegistry.addErrorPages(errorPage); } } public class ErrorProperties { @Value("${error.path:/error}") private String path = "/error";
/*返回頁面響應*/ @RequestMapping(produces = "text/html") 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()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); /*若是沒有找到對應的 /error/5xx.html 視圖 就會返回一個名爲 error 的ModelAndView*/ return (modelAndView != null ? modelAndView : new ModelAndView("error", model)); } /*返回json響應*/ @RequestMapping @ResponseBody public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); /*將響應狀態碼和響應數據封裝成ResponseEntity 返回*/ return new ResponseEntity<>(body, status); }
protected HttpStatus getStatus(HttpServletRequest request) { /*從request 獲取key爲 javax.servlet.error.status_code 的值*/ Integer statusCode = (Integer) request .getAttribute("javax.servlet.error.status_code"); if (statusCode == null) { /*獲取不到就響應一個500*/ return HttpStatus.INTERNAL_SERVER_ERROR; } try { return HttpStatus.valueOf(statusCode); } catch (Exception ex) { /*發生錯誤也響應一個500*/ return HttpStatus.INTERNAL_SERVER_ERROR; } }
public abstract class AbstractErrorController implements ErrorController { private final ErrorAttributes errorAttributes; protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) { WebRequest webRequest = new ServletWebRequest(request); /*就是調用ErrorAttributes 裏的 getErrorAttributes 方法獲取響應數據*/ return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace); } } public class ErrorMvcAutoConfiguration { /*爲咱們註冊了一個DefaultErrorAttributes */ @Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes( this.serverProperties.getError().isIncludeException()); } } //即調用了DefaultErrorAttributes 的 getErrorAttributes 方法,以下: @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap<>(); errorAttributes.put("timestamp", new Date()); addStatus(errorAttributes, webRequest); addErrorDetails(errorAttributes, webRequest, includeStackTrace); addPath(errorAttributes, webRequest); return errorAttributes; } /* 總的來講就是放了以下數據 頁面能獲取的信息; timestamp:時間戳 status:狀態碼 error:錯誤提示 exception:異常對象 message:異常消息 errors:JSR303數據校驗的錯誤都在這裏 */
public class ErrorMvcAutoConfiguration { @Configuration @ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true) @Conditional(ErrorTemplateMissingCondition.class) protected static class WhitelabelErrorViewConfiguration { //這個即咱們常常看到的錯誤頁 private final SpelView defaultErrorView = new SpelView( "<html><body><h1>Whitelabel Error Page</h1>" + "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>" + "<div id='created'>${timestamp}</div>" + "<div>There was an unexpected error (type=${error}, status=${status}).</div>" + "<div>${message}</div></body></html>"); // 便是這個View @Bean(name = "error") @ConditionalOnMissingBean(name = "error") public View defaultErrorView() { return this.defaultErrorView; } }
①,編寫一個錯誤處理類,這裏仍是用到了@ControllerAdvice 註解web
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; //聲明是一個錯誤處理器 @ControllerAdvice public class ErrorControllAdvice { // 處理那種類型的錯誤 @ExceptionHandler(Exception.class) public String errorHandle(HttpServletRequest request){ // 添加額外錯誤響應數據 Map<String,Object> map=new HashMap<>(); // 添加一個公司的響應數據 map.put("company","小米"); // 放到request 中,而後在咱們定製的DefaultErrorAttributes 裏獲取 request.setAttribute("ext",map); // 設置響應狀態碼 request.setAttribute("javax.servlet.error.status_code",500); // 轉發到/error 請求,這樣就能根據客戶端優化接收何種類型數據,決定響應html仍是json return "forward:/error"; } }
②,定製DefaultErrorAttributesspring
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.WebRequest; import java.util.Map; @Component public class MyDefaultErrorAttributes extends DefaultErrorAttributes { /*重寫getErrorAttributes 方法*/ @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { // 獲取原來的響應數據 Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace); //從請求域中拿值 Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", RequestAttributes.SCOPE_REQUEST); //添加咱們定製的響應數據 map.put("ext",ext); // 返回帶有咱們定製的數據的map return map; } }