SpringBoot异常自动处理机制
ErrorMvcAutoConfiguration中注入了四个重要的类,分别是
DefaultErrorAttributes(用处共享页面中的数据)、BasicErrorController、ErrorPageCustomizer、DefaultErrorViewResolver四个类。
当发生错误时,
1、ErrorPageCustomer会将请求转发到/error地址中,
2、然后通过BasicErrorController来处理,最终的处理结果是返回一个ModelAndView(指定了转发的路径和需要渲染的数据)
这里SpringBoot是通过请求头中的信息来辨别是浏览器还是客户端
-
如果是浏览器请求则请求BasicErrorController.errorHtml方法,响应的是一个ModelAndView。
-
如果是客户端请求则请求BasicErrorController.error方法,响应的是JSON形式的数据
(这里以浏览器请求为例子)
2.1、调用BasicErrorController.errorHtml(HttpServletRequest request,HttpServletResponse response)方法
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
// 此处调用了getErrorAttributes来得到model,这里的getErrorAttributes就是之前注入的四个Bean之一,DefaultErrorAttributes中的方法。此方法中可以获取状态码、时间戳、错误提示等
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
}
2.2、errorHtml方法中调用了AbstractErrorController.resolveErrorView(HttpServletRequest request,HttpServletResponse response, HttpStatus status, Map<String, Object> model)方法。
// AbstractErrorController.resolveErrorView方法
protected ModelAndView resolveErrorView(HttpServletRequest request,
HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
// (注意:这里的ErrorMvcAutoConfiguration.errorViewResolvers和AbstractErrorController.errorViewResolvers是同一个,是ErrorMvcAutoConfiguration在注入Bean的时候通过构造方法传入的)
for (ErrorViewResolver resolver : this.errorViewResolvers) {
// 遍历所有的视图解析器,找到适合的解析器
// 这里调用了DefaultErrorViewResolver.resolveErrorView方法
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
// 如果所有的视图解析器都无法解析则返回null
return null;
}
2.3、在遍历所有视图解析器的时候调用了DefaultErrorViewResolver.resolveErrorView方法
// DefaultErrorViewResolver.resolveErrorView
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
// 首先传入具体的状态码来解析,如404
ModelAndView modelAndView = resolve(String.valueOf(status), model);
// 如果视图为空,并且错误代码为4xx或者5xx则传入状态码和model进行解析
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
// DefaultErrorViewResolver.resolve方法
private ModelAndView resolve(String viewName, Map<String, Object> model) {
// 根据传入的状态码去 "error/" 下寻找是否有对应页面
String errorViewName = "error/" + viewName;
// 模板引擎可以解析这个页面地址就用模板引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
// 模板引擎可用的情况下返回到errorViewName指定的视图地址
return new ModelAndView(errorViewName, model);
}
// 模板引擎不可用则在静态资源文件夹下寻找errorViewName对应的页面
return resolveResource(errorViewName, model);
}
// 去静态资源文件夹下找对应的页面,找不到则返回null
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;
}
2.4、注意看2.1步骤中的代码,如果最后解析返回一个ModelAndView则会直接渲染后响应给请求,如果返回的是null,则会生成一个名为 “error” 的 ModelAndView ,也就是我们熟知的默认错误页面。
@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>");
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}
// If the user adds @EnableWebMvc then the bean name view resolver from
// WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
@Bean
@ConditionalOnMissingBean(BeanNameViewResolver.class)
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
}
如何定制错误的响应信息
定制错误页面
在上面也2.3中有分析到DefaultErrorViewResolver中视图解析器是如何解析的,**有模板引擎的情况下直接将对应错误状态码的html页面放入error文件夹下即可。**也可以使用 “4xx.html” 或者"5xx.html" 来处理一种类型的错误。没有模板引擎则直接去静态资源文件夹下找,如果静态资源也找不到则返回SpringBoot的默认错误页面