起因
最近观察生产日志发现偶尔会看到一个/error接口的错误异常,但是从异常堆栈信息里没有找到任何关于真实发生错误的接口路径,以至于排查问题时无从下手。
通过Postman复现一下,效果如下:
带着如下几个问题来拆分ErrorPage的实现方式:
- 什么场景会触发ErrorPage页面
- ErrorPage的实现核心组件包括哪些
ErrorController
通过查找,定位到自己代码依赖着一个自实现的common包,其中有个CustomerErrorController,其访问路径为:
${server.error.path:${error.path:/error}}
在配置文件没有配置server.error.path以及error.path时,路径为/error,逻辑上符合复现的场景,通过debug也确实可以证实。
CustomerErrorController网上找最终发现顶级接口就是ErrorController,利用IDEA工具可以快速看到默认实现的类
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController{ ... }
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
this.errorViewResolvers);
}
从ServerProperties中找到ErrorProperties,其中默认配置的path为/error,与CustomerErrorController的路径是相同的。
ErrorMvcAutoConfiguration
/// 核心的组件
ErrorPageCustomizer
ErrorPageRegistrarBeanPostProcessor
ErrorPageFilter
TomcatEmbeddedServletContainerFactory
TomcatErrorPage
// 在ErrorPageFilter中找到
private void setErrorAttributes(HttpServletRequest request, int status,
String message) {
request.setAttribute(ERROR_STATUS_CODE, status);
request.setAttribute(ERROR_MESSAGE, message);
// 获取到uri就可以知道是哪个接口触发了这段跳转至/error接口
request.setAttribute(ERROR_REQUEST_URI, request.getRequestURI());
}
// 这几个参数在自定义的ErrorController中是可以读取到,
// 但是通过Debug发现并没有运行到ErrorPageFilter,
// 意味着类似的代码可能在另外的地方也有一份
回到前文提到的两个问题。
什么场景会触发ErrorPage?由于没有找到真正核心的代码,参考ErrorPageFilter的代码如下:(想要一探究竟,考虑从DispatchServlet的流程上去找)
private void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
ErrorWrapperResponse wrapped = new ErrorWrapperResponse(response);
try {
chain.doFilter(request, wrapped);
if (wrapped.hasErrorToSend()) {
handleErrorStatus(request, response, wrapped.getStatus(),
wrapped.getMessage());
response.flushBuffer();
}
else if (!request.isAsyncStarted() && !response.isCommitted()) {
response.flushBuffer();
}
}
catch (Throwable ex) {
Throwable exceptionToHandle = ex;
if (ex instanceof NestedServletException) {
exceptionToHandle = ((NestedServletException) ex).getRootCause();
}
handleException(request, response, wrapped, exceptionToHandle);
response.flushBuffer();
}
}
ErrorPage包括哪些核心组件?参照ErrorMvcAutoConfiguration
网友评论