SpringBoot定製錯誤頁面及原理
一、SpringBoot預設的錯誤處理機制
1)、瀏覽器返回的預設錯誤頁面如下:
☞ 瀏覽器傳送請求的請求頭資訊如下:text/html會在後面的原始碼分析中說到。
2)、如果是其他客戶端,預設則響應錯誤的JSON字串,如下所示:
☞ 其他客戶端傳送請求的請求頭資訊如下:" */* "原始碼中解釋。
二、原理分析:參照ErrorMvcAutoConfiguration類:錯誤處理的自動配置類,以下4項為此類的重要資訊。
1)、ErrorMvcAutoConfiguration.ErrorPageCustomizer:當系統出現4xx或者5xx之類的錯誤時,ErrorPageCustomizer就會生效(定製錯誤的響應規則),根據如下原始碼可知,將會來到/error請求。
@Bean public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() { return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties); } //進入ErrorPageCustomizer方法,發現registerErrorPages方法:註冊一個錯誤也 private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered { private final ServerProperties properties; protected ErrorPageCustomizer(ServerProperties properties) { this.properties = properties; } public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix() + this.properties.getError().getPath()); errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage}); } } //進入this.properties.getError().getPath()方法,獲取如下資訊,得到/error請求。 @Value("${error.path:/error}") private String path = "/error";//系統出現錯誤以後來到error請求進行處理;(web.xml註冊的錯誤頁面規則)
2)、BasicErrorController處理/error錯誤請求:注意:text/html和*/*就是在此處生效。
@Bean @ConditionalOnMissingBean( value = {ErrorController.class}, search = SearchStrategy.CURRENT ) public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) { return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers); } //進入BasicErrorController物件,獲取如下資訊 @Controller @RequestMapping({"${server.error.path:${error.path:/error}}"}) public class BasicErrorController extends AbstractErrorController { private final ErrorProperties errorProperties; public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) { this(errorAttributes, errorProperties, Collections.emptyList()); } @RequestMapping( produces = {"text/html"}//產生html型別的資料;瀏覽器傳送的請求來到這個方法處理 ) 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 @ResponseBody//產生json資料,其他客戶端來到這個方法處理; 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); }
☞ 如上程式碼中提到的錯誤頁面解析程式碼,進入此方法: this.resolveErrorView(request, response, status, model);
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
Iterator var5 = this.errorViewResolvers.iterator();
ModelAndView modelAndView;
do {
//從所有的ErrorViewResolver得到ModelAndView
if(!var5.hasNext()) {
return null;
}
ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
modelAndView = resolver.resolveErrorView(request, status, model);
} while(modelAndView == null);
return modelAndView;
}
3)、最總的響應頁面是由DefaultErrorViewResolver解析得到的:最重要的資訊是,SpringBoot預設模板引擎的/error目錄下獲取‘status’.xml錯誤頁面,也可以通過4xx.xml來統配404.xml和400.xml等等,但是優先獲取精準的頁面。如果模板引擎中不存在,則會從靜態頁面中獲取錯誤頁面。否則返回系統預設錯誤頁面。
@Bean
@ConditionalOnBean({DispatcherServlet.class})
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}
//進入DefaultErrorViewResolver類中
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = this.resolve(String.valueOf(status), model);
if(modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
//呼叫時viewname = status ***重要
modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//預設SpringBoot可以去找到一個頁面? error/404
String errorViewName = "error/" + viewName;
//模板引擎可以解析這個頁面地址就用模板引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.
getProvider(errorViewName, this.applicationContext);
//模板引擎可用的情況下返回到errorViewName指定的檢視地址,
//當模板引擎不可用,就在靜態資原始檔夾下找errorViewName對應的頁面 error/404.html
return provider != null?new ModelAndView(errorViewName, model):this.resolveResource(errorViewName, model);
}
4)、DefaultErrorAttributes:在頁面新增錯誤資訊,供我們使用。
@Bean
@ConditionalOnMissingBean(
value = {ErrorAttributes.class},
search = SearchStrategy.CURRENT
)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
//進入DefaultErrorAttributes類中,發現此方法給檢視中添加了status狀態等資訊,供我們使用。
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());
this.addStatus(errorAttributes, requestAttributes);
this.addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
this.addPath(errorAttributes, requestAttributes);
return errorAttributes;
}
三、定製錯誤JSON資料
1)、自定義異常處理類,返回定製的JSON資料。通過上述的分析,我們得知:①、可以完全編寫一個ErrorController的實現類,或者繼承AbstractErrorController的子類,放入容器中。②、也可以自定義異常處理類,返回JSON資料。③、頁面上的資料或JSON返回的資料都是可以通過errorAttributes.getErrorAttributes得到的。我們可以自定義屬於自己的ErrorAttributes。
//首先我們可以通過自定義異常處理,來確定返回的資料,但這個不夠靈活,我們可以與③結合使用
/**
* @RequestMapping啟動應用後,被 @ExceptionHandler、@InitBinder、@ModelAttribute 註解的方法,都會作用在 被 @RequestMapping
* 註解的方法上。
*/
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handlerException(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<String,Object>();
request.setAttribute("javax.servlet.error.status_code","500");
map.put("code","user.notexist");
map.put("message",e.getMessage());
return map;
}
}
//③自定義ErrorAttributes,一定要加入容器
@Component
public class MyErrorAttributes extends DefaultErrorAttributes{
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
//獲取預設的配置,在此基礎上新增自己的需求
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
//自定義自己需要的屬性
map.put("company","yintong");
//獲取我們在異常處理類中新增的資訊,
/*注意:當我們需要結合使用的時候異常處理必須return "forward:/error";將請求轉發出去,不能直接返回map物件,
同時要去掉@responseBody註解,否則ErrorAttributes不生效*/
map.put("ext",requestAttributes.getAttribute("ext",requestAttributes.SCOPE_REQUEST));
return map;
}
}
2)、效果展示: