前言
自己在开发实际项目中,需要这样的全局错误处理需求:对html请求错误返回通用错误页面,屏蔽详细信息。对REST请求错误返回JSON数据,包含错误信息。通过查阅文档,我无法找到解决方案,因此我不得不从上到下理解Spring Boot的错误处理逻辑,寻找解决方案。
版本:Spring Boot 1.5.x。
servlet的errorpage机制
首先看看最底层的Servlet API提供的错误处理机制。
错误原因
Servlet容器捕获到了异常或使用HttpServletResponse.sendError()
返回HTTP错误状态。
错误处理
Servlet提供了errorpage
的错误处理机制,提供了基于status code/exception type到resource的映射的错误处理方式。
<error-page>
<error-code>404</error-code>
<location>/pageNotFound</location>
</error-page>
这种映射通过转发实现,会在request域中附带相关错误信息以便渲染:
javax.servlet.error.exception 错误异常参数
javax.servlet.error.exception_type 错误异常类型
...
参考:https://tomcat.apache.org/tomcat-7.0-doc/servletapi/constant-values.html
问题是web.xml已经被时代抛弃,而servlet3.0也没有提供相关的Java接口来提供该功能。
Spring Boot Web错误处理
由于Servlet缺乏相关的Java API来满足该功能,因此Spring Boot提供了自己封装的一套Java API。采取了相同的处理逻辑:
- 支持对status code/exception进行错误处理
- 采取转发的错误处理方式
处理机制
Spring Boot Web错误处理机制是独立于MVC框架的,包括:
ErrorPageRegistrar
错误页面注册者ErrorPageRegistry
错误页面注册表ErrorPage
错误页面
嵌入式Servlet容器
对于jar包和war包,Spring Boot分别提供了不同的实现方式。当采取嵌入式Servlet容器时,容器工厂实现了错误页面注册表:
public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry {
}
容器工厂在初始化时会注册ErrorPages,这往往依赖于具体的底层实现,如Tomcat会调用其相关API转换并注册为其原生的ErrorPage实现。以Tomcat为例:
TomcatEmbeddedServletContainerFactory:
protected void configureContext(Context context,
ServletContextInitializer[] initializers) {
//...
for (ErrorPage errorPage : getErrorPages()) {
new TomcatErrorPage(errorPage).addToContext(context);
}
//...
}
TomcatErrorPage:
public void addToContext(Context context) {
Assert.state(this.nativePage != null,
"Neither Tomcat 7 nor 8 detected so no native error page exists");
if (ClassUtils.isPresent(ERROR_PAGE_CLASS, null)) {
org.apache.tomcat.util.descriptor.web.ErrorPage errorPage = (org.apache.tomcat.util.descriptor.web.ErrorPage) this.nativePage;
errorPage.setLocation(this.location);
errorPage.setErrorCode(this.errorCode);
errorPage.setExceptionType(this.exceptionType);
context.addErrorPage(errorPage);
}
else {
callMethod(this.nativePage, "setLocation", this.location, String.class);
callMethod(this.nativePage, "setErrorCode", this.errorCode, int.class);
callMethod(this.nativePage, "setExceptionType", this.exceptionType,
String.class);
callMethod(context, "addErrorPage", this.nativePage,
this.nativePage.getClass());//注册为容器原生的错误页面
}
}
这其实是容器对servlet errorpage的底层实现,委托给容器对Servlet API的支持来实现错误处理。
war包
而如果是独立部署的war包,Spring Boot则使用Filter来实现。具体为ErrorPageFilter:
public class ErrorPageFilter implements Filter, ErrorPageRegistry {
//...
}
配置类
以上介绍了Spring Boot Web提供的类似Servlet原生的错误处理机制。下面介绍具体的实现。对于Spring MVC,错误处理实现实现可以在ErrorMvcAutoConfiguration
类找到。其主要配置了:
- ErrorPageCustomizer,它会注册一个global ErrorPage,匹配
/error
路径,作为缺省设置。 - 包含一个
WhitelabelErrorViewConfiguration
,它配置了缺省的html错误视图。 - 包含一个
DefaultErrorViewResolverConfiguration
,它将常见的错误状态码映射为/error/xxx
视图名。 - 配置了
BasicErrorController
,接收路径为/error
的请求。
最上层SpringMVC Web错误处理
mvc对处理器异常进行了处理,并提供了@ControllerAdvice
来实现处理器层对Exception
的处理。
总结
Servlet层提供了对错误状态码和异常的处理。错误状态码通过request.sendError()
触发,处理逻辑实现由servlet容器提供。
由于servlet3.0缺乏errorpage的Java API支持,Spring Boot封装了一套。对于嵌入式Servlet容器,调用servlet容器对errorpage的底层支持实现;对于war包,通过Servlet Filter来实现。
Spring Boot的默认处理机制是转发给映射到/error
的Controller来处理。
Spring MVC本身在Controller层又提供了异常处理机制。