5.SpringBoot 错误页面
1.默认效果
当我们在运行SpringBoot的时候,访问一个不存在的页面,SpringBoot默认为我们返回一个空白页面,如下所示
这个空白页面主要包括 默认错误路径、时间戳、错误提示消息 和错误状态码
但如果我们使用其他的客户端(非浏览器),例如 Postman工具 发送 http://127.0.0.1:8080/noPage 请求时,默认响应客户端的是JSON数据,如下图所示
2.原理
出现上面的两种默认效果的原因是 SpringBoot为我们自动配置了 错误处理自动配置的 控制器ErrorMvcAutoConfiguration
首先,我们ErrorMvcAutoConfiguration 为我们配置了这几个组件
- DefaultErrorAttributes
- BasicErrorController
- ErrorPageCustomizer
- DefaultErrorViewResolver
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
errorViewResolvers.orderedStream().collect(Collectors.toList()));
}
@Bean
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
}
@Configuration(proxyBeanMethods = false)
static class DefaultErrorViewResolverConfiguration {
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,
ResourceProperties resourceProperties) {
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
}
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}
}
ErrorPageCustomizer
@Bean
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
}
首先,它会实例化出一个 ErrorPageCustomizer对象,在ErrorPageCustomizer对象中有一个重要的方法registerErrorPages
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(
this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(errorPage);
}
然后就会调用该方法,注册一个错误页面, 通过 getPath()获取到路径,getPath存在默认的值为"/error",如果在配置文件中配值了``server.error.path=/errors那就会从配置文件中取出error.path`的值
@Value("${error.path:/error}")
private String path = "/error";

- 总结
ErrorPageCustomizer组件的作用就是 当 用户请求一旦发生错误时,就会获得 path (错误页面的路径),然后将我们转发到 path 路径下。 并将该路径信息发给BasicErrorController进行处理
BasicErrorController
从名字上,我们可以知道它是一个 基本的错误控制器。 核心代码如下:
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
@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);
}
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
它是用来处理 配置文件下的 server.error.path的请求,如果 server.error.path 未配置时,就会处理 配置文件下的 error.path的请求,如果也没配置,就会处理/error
在这个控制器组件下面 存在着两个 @RequestMapping ,都是处理同一个请求路径的 方法。只是这两个方法的返回值不一样
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE) // 产生html类型的数据
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response)
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
TEXT_HTML_VALUE = "text/html
但是竟然这两种方式都是用于处理一个请求的,为什么浏览器获取的数据和postMan获取的会不一样呢?
原因是浏览器发送的请求的请求头包含这串信息
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
也就是说 浏览器优先接收 text/html类型的数据。
而postMan发送请求时 的请求头如下,它没有指明 希望接收的数据类型,所以服务端给它返回一个JSON类型的数据
那重点来了,返回页面时,又是如何实现的呢?
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE) // TEXT_HTML_VALUE = "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);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
当用户发送请求,发送错误时
- 首先,它会通过请求获取响应状态码和一些model数据,,然后返回一个
modelAndViews, 通过第7行,我们可以知道modelAndViews是通过调用resolveErrorView()方法获得的。modelAndViews包含了需要返回的地址,页面等信息 - 响应页面是调用了
resolveErrorView(),如下
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,Map<String, Object> model) {
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
第2行:遍历所有的 ErrorViewResolver ,调用resolver. resolveErrorView(request, status, model) 如果存在着 一个 modelAndView 就返回.
在之前,Spring Boot 为我们自动注册了一个 DefaultErrorViewResolver 到容器中,也就是说resolveErrorView() 方法将会由 DefaultErrorViewResolver 进行调用。
DefaultErrorViewResolver
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
private static final Map<Series, String> SERIES_VIEWS;
static {
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);
}
@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;
}
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);
}
首先,我们可以发现 第5-10行 ,为map中添加了一下客户端错误,用"4xx"表示;服务端错误,用"5xx"表示。
然后就会调用 第13-19行的 resolveErrorView()方法,解析错误视图。
因为发生了错误,所以modelAndView 自然就是等于 null 了,那么就会调用第16行的resolve()方法进行解析,并返回一个modelAndView对象。
resolve(SERIES_VIEWS.get(status.series()), model)
private ModelAndView resolve(String viewName, Map<String, Object> model)
调用resolve()方法时,传入了两个参数,分别为视图名称和model数据
第23行:this.templateAvailabilityProviders.getProvider(errorViewName,this.applicationContext)如果模板引擎 根据这个错误视图名称能找到对应的模板时,就用模板引擎进行解析
如果解析的结果不为空时,即解析成功过,就将模板引擎解析的 modelAndView的结果进行返回,否者就调用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;
}
调用resolveResource方法的时候,从第2行,我们可以发现它会在静态资源下找一个资源名为 错误状态码.html的静态资源(如果我们的错误状态码为404,那么它就会 静态资源文件夹下找 404.html)
3.错误页面总结:
1)、有模板引擎的情况下;error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;
2)我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);
-
有模板引擎的情况下,首先会在
templates/error/文件下获取状态码.html文件 -
如果
状态码.html文件不存在的情况下,templates/error/文件夹下获取 状态码对应的 **“4xx.html”或"5xx.html"**文件。
-
如果都不存在的话会在一下静态资源文件夹 获取资源, 精确优先(优先寻找精确的状态码.html),否则查早对应的 **“4xx.html”或"5xx.html"**文件
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"}; -
如果以上都不能获取,则返回默认的 Whitelabel Error Page
-
页面能获取的信息;
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里

本文详细解析了Spring Boot错误页面的工作原理,包括DefaultErrorAttributes、BasicErrorController和ErrorPageCustomizer等组件的作用。介绍了在浏览器和非浏览器客户端下不同响应形式的原因,并探讨了如何自定义错误页面,包括模板引擎下的错误视图解析和静态资源的查找顺序。最后总结了错误页面的配置策略和展示信息。
3276





