SpringBoot异常处理之ErrorPageFilter

ErrorPageFilter 简介

ErrorPageFilter是SpringBoot在1.4.0版本提供的一个类,本质上是一个Filter。 它的作用主要有两方面:

  1. 提供应用程序注册ErrorPage的接口,此时它的角色是:ErrorPageRegistry
  2. 处理应用程序异常,根据异常的类型转发到对应的ErrorPage页, 从而不依赖部署的容器错误处理机制

ErrorPageFilter 类的工作原理

主要逻辑如下:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ErrorPageFilter implements Filter, ErrorPageRegistr {
private final OncePerRequestFilter delegate = new OncePerRequestFilter() {

truetrue@Override
truetrueprotected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
truetruetrueErrorPageFilter.this.doFilter(request, response, chain);
truetrue}

truetrue@Override
truetrueprotected boolean shouldNotFilterAsyncDispatch() {
truetruetruereturn false;
truetrue}

true};

true@Override
truepublic void init(FilterConfig filterConfig) throws ServletException {
truetruethis.delegate.init(filterConfig);
true}

true@Override
truepublic void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
truetruethis.delegate.doFilter(request, response, chain);
true}
true/**
* 过滤每个请求,如果请求在处理过程中发生异常则处理
*/
trueprivate void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
truetrueErrorWrapperResponse wrapped = new ErrorWrapperResponse(response);
truetruetry {
truetruetruechain.doFilter(request, wrapped);
truetrue}
truetruecatch (Throwable ex) {
truetruetrueThrowable exceptionToHandle = ex;
truetruetrueif (ex instanceof NestedServletException) {
truetruetruetrueexceptionToHandle = ((NestedServletException) ex).getRootCause();
truetruetrue}
truetruetruehandleException(request, response, wrapped, exceptionToHandle);
truetruetrueresponse.flushBuffer();
truetrue}
true}
trueprivate void handleException(HttpServletRequest request, HttpServletResponse response,
ErrorWrapperResponse wrapped, Throwable ex)
throws IOException, ServletException {
truetrueClass<?> type = ex.getClass();
truetrue//根据异常类型查询对应错误页的地址
truetrueString errorPath = getErrorPath(type);
truetrueif (errorPath == null) {
truetruetruerethrow(ex);
truetruetruereturn;
truetrue}
truetrueif (response.isCommitted()) {
truetruetruehandleCommittedResponse(request, ex);
truetruetruereturn;
truetrue}
truetrue//转发请求
truetrueforwardToErrorPage(errorPath, request, wrapped, ex);
true}
trueprivate void forwardToErrorPage(String path, HttpServletRequest request,
HttpServletResponse response, Throwable ex){
setErrorAttributes(request, 500, ex.getMessage());
request.setAttribute(ERROR_EXCEPTION, ex);
request.setAttribute(ERROR_EXCEPTION_TYPE, ex.getClass());
response.reset();
response.sendError(500, ex.getMessage());
//请求转发
request.getRequestDispatcher(path).forward(request, response);
request.removeAttribute(ERROR_EXCEPTION);
request.removeAttribute(ERROR_EXCEPTION_TYPE);
true}
true/**
* 该方法用来添加错误页信息, 是接口ErrorPageRegistry的方法
*/
true@Override
truepublic void addErrorPages(ErrorPage... errorPages) {
truetruefor (ErrorPage errorPage : errorPages) {
truetruetrueif (errorPage.isGlobal()) {
truetruetruetruethis.global = errorPage.getPath();
truetruetrue}
truetruetrueelse if (errorPage.getStatus() != null) {
truetruetruetruethis.statuses.put(errorPage.getStatus().value(), errorPage.getPath());
truetruetrue}
truetruetrueelse {
truetruetruetruethis.exceptions.put(errorPage.getException(), errorPage.getPath());
truetruetrue}
truetrue}
true}
}

ErrorPageFilter 相关类

ErrorPageRegistry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Interface for a registry that holds {@link ErrorPage ErrorPages}.
*
* @author Phillip Webb
* @since 1.4.0
*/
public interface ErrorPageRegistry {

true/**
* Adds error pages that will be used when handling exceptions.
* @param errorPages the error pages
*/
truevoid addErrorPages(ErrorPage... errorPages);

}

ErrorPage

该类相当于应用部署描述符web.xml中的error-page结点的Java类表示

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
/**
* Simple container-independent abstraction for servlet error pages. Roughly equivalent to
* the {@literal &lt;error-page&gt;} element traditionally found in web.xml.
*
* @author Dave Syer
* @since 1.4.0
*/
public class ErrorPage {

trueprivate final HttpStatus status;

trueprivate final Class<? extends Throwable> exception;

trueprivate final String path;

truepublic ErrorPage(String path) {
truetruethis.status = null;
truetruethis.exception = null;
truetruethis.path = path;
true}

truepublic ErrorPage(HttpStatus status, String path) {
truetruethis.status = status;
truetruethis.exception = null;
truetruethis.path = path;
true}

truepublic ErrorPage(Class<? extends Throwable> exception, String path) {
truetruethis.status = null;
truetruethis.exception = exception;
truetruethis.path = path;
true}
}

ErrorPageRegistrar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Interface to be implemented by types that register {@link ErrorPage ErrorPages}.
*
* @author Phillip Webb
* @since 1.4.0
*/
public interface ErrorPageRegistrar {

true/**
* Register pages as required with the given registry.
* @param registry the error page registry
*/
truevoid registerErrorPages(ErrorPageRegistry registry);

}

容器中实现了ErrorPageRegistrar接口的类会被自动注册到ErrorPageFilter中。

SpringBoot 错误页处理原理

有了上面的原理基础,我们就可以实现自己的错误页处理类(实现接口ErrorPageRegistrar并注册到spring容器中)。熟悉spring的肯定能猜到spring已经提供了这样的类,该类就是:ErrorPageCustomizer

ErrorPageCustomizer

ErrorPageCustomizer 实现了接口 ErrorPageRegistrar,该类会在SpringBoot启动过程中被注册到ErrorPageFilter。 它是如何注册的呢?

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
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
// Load before the main WebMvcAutoConfiguration so that the error View is available
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties(ResourceProperties.class)
public class ErrorMvcAutoConfiguration {

/**
* 这里就实现了Bean的注册
*/
@Bean
public ErrorPageCustomizer errorPageCustomizer() {
return new ErrorPageCustomizer(this.serverProperties);
}
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
private final ServerProperties properties;

protected ErrorPageCustomizer(ServerProperties properties) {
this.properties = properties;
}

@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix()
+ this.properties.getError().getPath());
errorPageRegistry.addErrorPages(errorPage);
}

@Override
public int getOrder() {
return 0;
}
}
}

ErrorPageCustomizer 默认会转发错误请求到/error, 这个可以在配置文件application.properties中进行设置:server.error.path

错误处理

当错误请求被转发到/error时, 该请求将会由SpringBoot提供的BasicErrorController进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//${server.error.path:${error.path:/error}}
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
@RequestMapping(produces = "text/html")
truepublic ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
truetrueHttpStatus status = getStatus(request);
truetrueMap<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
truetruetruetruerequest, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
truetrueresponse.setStatus(status.value());
truetrueModelAndView modelAndView = resolveErrorView(request, response, status, model);
truetruereturn (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
true}
}

最后就是渲染出SpringBoot默认的错误页面。一般来说我们需要自己处理/error请求来渲染我们自己提供的错误页。

文章目录
  1. 1. ErrorPageFilter 简介
  2. 2. ErrorPageFilter 类的工作原理
    1. 2.1. 主要逻辑如下:
  3. 3. ErrorPageFilter 相关类
    1. 3.1. ErrorPageRegistry
    2. 3.2. ErrorPage
    3. 3.3. ErrorPageRegistrar
  4. 4. SpringBoot 错误页处理原理
    1. 4.1. ErrorPageCustomizer
    2. 4.2. 错误处理
|