文章目录
1. 前言
在Spring boot无论是后端错误还是前端页面的错误,任何的错误页面都有相应的对应。在Spring boot中是如何控制错误页面的样式的呢?
2. 源码分析
首先看与错误处理有关的自动配置类ErrorMvcAutoConfiguration
该配置工作之前有两个前提条件:
- 该应用必须是一个
servlet-web
的应用 - Servlet和DispatcherServlet必须存在于classpath
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
继续看源码发现,该配置类给容器中注入了四个组件分别是
- DefaultErrorAttributes
- BasicErrorController
- ErrorPageCustomizer
- PreserveErrorControllerTargetClassPostProcessor(目的是为ErrorController bean的定义设置属性preserveTargetClass:true,从而确保ErrorController bean如果有代理对象的话代理对象总是代理在类上而不是接口上。)
1. 核心配置类分析
一个静态的内部注解类
DefaultErrorViewResolverConfiguration
该配置类源码,该配置类的内部其实是由DefaultErrorViewResolver
注入进去的。
@Configuration
static class DefaultErrorViewResolverConfiguration {
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,
ResourceProperties resourceProperties) {
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
}
注入的前提是:1. DispatcherServlet存在,并且DefaultErrorViewResolver不存在。
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}
}
DefaultErrorViewResolver
为一个默认的错误视图解析类,作用是决定去哪儿映射对应的页面。
其内部主要的方法的思路如下:
- 请求交由
resolveErrorView
寻找错误视图。 - 真正的页面处理动作由下面的
resolve
执行,具体是根据错误码去寻找对应的页面。 - 比如:如果返回错误码为404,则映射error/404,如果提供了模板引擎可以解析这个页面地址就用模板引擎解析;如果模板引擎不可用,则静态资源文件夹下找errorViewName对应的页面 error/404.html
- 在引擎不可用的时候,用
resolveResource
来寻找页面。
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
//该方法真正的视图映射方法。具体是根据错误码去寻找对应的页面。
//比如:如果返回错误码为404,则映射error/404
//如果提供了模板引擎可以解析这个页面地址就用模板引擎解析
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);
}
//如果模板引擎不可用,则静态资源文件夹下找errorViewName对应的页面 error/404.html
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;
}
2. 组件分析
1. DefaultErrorAttributes
核心思路:
- 当用户没有自己配置
Err@orAttributes
时候,DefaultErrorAttributes
才会生效。 DefaultErrorAttributes
通过getErrorAttributes
方法将对一个的属性添加进去。
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}
//添加对一个的错误属性
@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;
}
主要的错误信息
<ul>
<li>timestamp - The time that the errors were extracted</li>
<li>status - The status code</li>
<li>error - The error reason</li>
<li>exception - The class name of the root exception (if configured)</li>
<li>message - The exception message</li>
<li>errors - Any {@link ObjectError}s from a {@link BindingResult} exception
<li>trace - The exception stack trace</li>
<li>path - The URL path when the exception was raised</li>
</ul>
2. BasicErrorController
该组件的核心作用如下:
- 当用户自己没有配置
ErrorController
时,BasicErrorController
才会生效。 - 该
controller
针对于浏览器和json格式的请求做不同样式的处理
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
}
(1):产生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());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
(2):产生json数据,其他客户端来到这个方法处理;
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body, status);
}
3. ErrorPageCustomizer
配置错误页面,同样由内部类ErrorPageCustomizer
实现,该类实现了接口ErrorPageRegistrar
,该接口实际上是当前Servlet容器对象的的工厂。
系统出现错误以后来到error请求进行处理;
@Bean
public ErrorPageCustomizer errorPageCustomizer() {
return new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
}
ErrorPageCustomizer
的核心方法,为注册器提供一个错误页面获取方法,获取对应的controller的访问路径
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(
this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(errorPage);
}
@Value("${error.path:/error}")
private String path = "/error";
3. 如何自定义错误响应
1. 自定义错误页面
1)、如何定制错误的页面;
1)、有模板引擎的情况下;error/状态码;
【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;
我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);
页面能获取的信息;
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里
2)、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;
3)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;