DefaultErrorViewResolver
是Springboot
缺省实现的一个错误视图解析器ErrorViewResolver
。它基于一些常见的约定,尝试根据HTTP
错误状态码解析出错误处理视图。它会在目录/error
下针对提供的HTTP
错误状态码搜索模板或者静态资源,比如,给定了HTTP
状态码404
,它会尝试搜索如下模板或者静态资源:
/<templates>/error/404.<ext>
– 这里<templates>
表示所配置的模板所在目录,<ext>
表示所用的模板的文件名/<static>/error/404.html
– 这里<static>
表示静态资源文件所在路径/<templates>/error/4xx.<ext>
/<static>/error/4xx.html
源代码
package org.springframework.boot.autoconfigure.web.servlet.error;
// 省略 imports
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
private static final Map<Series, String> SERIES_VIEWS;
static {
// HTTP 错误状态码所在状态码段所对应的视图名称
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);
}
private ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final TemplateAvailabilityProviders templateAvailabilityProviders;
private int order = Ordered.LOWEST_PRECEDENCE;
/**
* Create a new DefaultErrorViewResolver instance.
* @param applicationContext the source application context
* @param resourceProperties resource properties 配置参数属性,对应 spring.resources.*
*/
public DefaultErrorViewResolver(ApplicationContext applicationContext,
ResourceProperties resourceProperties) {
Assert.notNull(applicationContext, "ApplicationContext must not be null");
Assert.notNull(resourceProperties, "ResourceProperties must not be null");
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
// 根据当前应用上下文检测所使用的模板技术的工具
this.templateAvailabilityProviders = new TemplateAvailabilityProviders(
applicationContext);
}
DefaultErrorViewResolver(ApplicationContext applicationContext,
ResourceProperties resourceProperties,
TemplateAvailabilityProviders templateAvailabilityProviders) {
Assert.notNull(applicationContext, "ApplicationContext must not be null");
Assert.notNull(resourceProperties, "ResourceProperties must not be null");
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.templateAvailabilityProviders = templateAvailabilityProviders;
}
// 接口 ErrorViewResolver 约定的方法,根据HTTP错误状态码尝试解析出一个错误处理视图,最终
// 以 ModelAndView 对象形式返回
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
// 先严格使用所提供的HTTP错误状态字status作为视图名称解析相应的错误处理视图
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
// 如果严格使用所提供的HTTP错误状态字status作为视图名称没有找到相应的错误处理视图,
// 现在尝试根据HTTP错误状态字status所在的HTTP状态字段(比如 404 对应 4xx, 500 对应 5xx)
// 作为视图名称解析相应的错误处理视图
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
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;
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
/**
* View backed by an HTML resource.
* 对应一个HTML静态资源文件的View实现。render动作其实就是把静态资源文件内容输出到 response,
* 所使用的 ContentType 时 text/html
*/
private static class HtmlResourceView implements View {
private Resource resource;
HtmlResourceView(Resource resource) {
this.resource = resource;
}
@Override
public String getContentType() {
return MediaType.TEXT_HTML_VALUE;
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.setContentType(getContentType());
FileCopyUtils.copy(this.resource.getInputStream(),
response.getOutputStream());
}
}
}