一、正常出现异常

Controller:

1
2
3
4
5
6
7
8
9
10
@RestController
public class ErrorController {

@GetMapping("/diyError")
public String error() {
int i = 2 / 0;
return "diyError";
}

}

结果:

image-20200819140914040

可以看见此错误页是 SpringBoot 提供的,由于我们没有提供 /error 页面,顺便展示我们出现的错误

二、静态页面异常

static 目录下新建一个 error 目录,并在其中新建 404.html 和 500.html:

404.html:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>404</h1>
</body>
</html>

500.html:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>500</h1>
</body>
</html>

再次访问 http://localhost:8080/diyError

可以看见已经进入自定义的静态页面了:
image-20200819142425408

再访问一个不存在的页面:

image-20200819142448711

注:也可以只定义 4xx.html 与 5xx.html 的静态页面,只要发生 400-499 的状态码或者 500-599 的状态码就对应 4xx.html 与 5xx.html 的结果

三、动态页面异常

引入 thymeleaf 的依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

templates 目录下新建一个 error 目录,并在其中新建 404.html、 500.html、 4xx.html、5xx.html :

image-20200819144108160

再次访问 http://localhost:8080/diyError

可以看见已经进入自定义的动态页面了:

image-20200819144234652

image-20200819144308646

优先级:

  • 如果有 404 的页面,会先找 404.html;如果没有就会找 4xx.html
  • 如果动态页面和静态页面同时存在,会优先使用动态页面

完整顺序大概如下:

发生了500错误 –> 查找动态 500.html 页面 –> 查找静态 500.html –> 查找动态 5xx.html –> 查找静态 5xx.html

四、SpringBoot 怎么自动配置

关键类:ErrorMvcAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}

}

在默认的错误视图解析器 DefaultErrorViewResolver 中:

1
2
3
4
5
6
7
8
@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;
}

如果 modelAndView 为空,即没找到对应的具体 404.html,就会用 SERIES_VIEWS.containsKey(status.series()) 找 4xx.html

1
2
3
4
5
6
7
8
9
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);
}
  • 判断 error 文件夹下面是否有对应的状态码的文档
  • 再判断是否有动态页面,即 TemplateAvailabilityProvider
  • 如果是动态页面,就返回动态页面;否则返回静态页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}

五、自定义异常数据

ErrorMvcAutoConfiguration 中,处理异常数据的方法:

1
2
3
4
5
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}

DefaultErrorAttributes() 中通过设置参数得到相应的结果

例如:将 error 中的 5xx.html 改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>5xx</h1>
<table border="1">
<tr>
<td>path</td>
<td th:text="${path}"></td>
</tr>
<tr>
<td>error</td>
<td th:text="${error}"></td>
</tr>
<tr>
<td>message</td>
<td th:text="${message}"></td>
</tr>
<tr>
<td>timestamp</td>
<td th:text="${timestamp}"></td>
</tr>
<tr>
<td>status</td>
<td th:text="${status}"></td>
</tr>
<tr>
<td>trace</td>
<td th:text="${trace}"></td>
</tr>
</table>
</body>
</html>

此时仔运行结果:

image-20200819162419826

再看一下使 DefaultErrorAttributes 生效的条件:

@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)

所以只要我们自定义 ErrorAttributes.class 就可以实现自己定义的异常数据处理

自定义 ErrorAttributes 有两种方式 :

  1. 直接实现 ErrorAttributes 接口
  2. 继承 DefaultErrorAttributes(推荐),因为 DefaultErrorAttributes 中对异常数据的处理已经完成,开发者可以直接使用。

新建一个配置类继承 DefaultErrorAttributes

1
2
3
4
5
6
7
8
9
10
@Component
public class MyErrorConfig extends DefaultErrorAttributes {

@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
errorAttributes.put("myerror", "自定义异常");
return errorAttributes;
}
}

在 5xx.html 中加入:

1
2
3
4
<tr>
<td>myerror</td>
<td th:text="${myerror}"></td>
</tr>

运行结果:可以看到此时的自定义的 error 已经加入

评论