今天终于把 boot 的异常处理完全研究透了:

boot提供了很多错误的处理工作。默认情况下,我们会看到一个whiteLabel(白标)的页面。 这个可能不是我们所需。因此我们需要定制。我于是做了个深入的研究。
boot 的错误,入口,显然是ErrorMvcAutoConfiguration。 它在WebMvcAutoConfiguration 配置之前完成 :
@AutoConfigureBefore(WebMvcAutoConfiguration.class) 在ErrorMvcAutoConfiguration中, 还注册了很多的 error 相关bean:
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(); // 注册了一个 专门收集 error 发生时错误信息的bean DefaultErrorAttributes 实现了HandlerExceptionResolver, 通过对异常的处理, 填充 错误属性 ErrorAttributes 。 这个是boot 中的 controller 出现异常的时候, 会使用到的
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
} @Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(), // 注册 BasicErrorController。 BasicErrorController 完成对所有 controller 发生异常情况的处理, 包括 异常和 4xx, 5xx 子类的 。
this.errorViewResolvers);
} @Bean
public ErrorPageCustomizer errorPageCustomizer() {
return new ErrorPageCustomizer(this.serverProperties); //注册 错误页面的 定制器。 后面会再次讨论这个 Customizer
} @Bean
public static PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() {
return new PreserveErrorControllerTargetClassPostProcessor(); // 这个有些难懂, 略去
} DefaultErrorViewResolverConfiguration @Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, // DefaultErrorViewResolver 是什么? 它提供了对err ViewResolver 处理的默认支持, 基本上就是说 它会返回一个 error view
this.resourceProperties); // DefaultErrorViewResolver 会作为 ErrorViewResolver 注入到 ErrorMvcAutoConfiguration 的构造中去。 而 errorViewResolvers 其实就是直接 交给了 BasicErrorController。 也就是说, BasicErrorController 处理错误的时候, 会使用 DefaultErrorViewResolver 提供的内容来进行 页面渲染。
} 说说 DefaultErrorViewResolver, 它是一个纯 boot 的内容。 它的处理方式是比较古怪的。它专门处理发生 error时候的 view 你给我一个error view, 我 就先去 error/ 目录下面去找 error/ + viewName + .html 的文件(这里的viewName通常是 , 之类的 错误的response status code), 找到了 就直接展示(渲染)它。 否则就 尝试去匹配4xx, 5xx,然后去找error/4xx.html或者 error/5xx.html 两个页面,找到了就展示它。 BasicErrorController 就是一个 Controller: @Controller
@RequestMapping("${server.error.path:${error.path:/error}}") // 这个我们都看得懂吧!!!
public class BasicErrorController extends AbstractErrorController { 关键是其中的两个方法: @RequestMapping(produces = "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))); // 这里的 model 是相关错误信息
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model); // 这个完成了具体的 处理过程
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView); // 如果找不到, 那还是返回一个 new ModelAndView("error", model) 吧
} @RequestMapping
@ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request); // 这里相对上面的方法,简单很多, 它不会去使用 viewResolver 去处理, 因为它不需要任何的 view ,而是直接返回 text 格式数据, 而不是 html 格式数据
return new ResponseEntity<Map<String, Object>>(body, status);
} resolveErrorView 方法是 AbstractErrorController 提供的: protected ModelAndView resolveErrorView(HttpServletRequest request,
HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
for (ErrorViewResolver resolver : this.errorViewResolvers) { // 正是这里 用到了之前 的 errorViewResolvers
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
} 上面的 resolver 是之前注册的 DefaultErrorViewResolver, 其resolveErrorView 方法是:
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = this.resolve(String.valueOf(status), model);
if(modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = this.resolve((String)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);
return provider != null?new ModelAndView(errorViewName, model):this.resolveResource(errorViewName, model);
} private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
String[] var3 = this.resourceProperties.getStaticLocations(); // 静态的 location 包括 "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"
    int var4 = var3.length;

    for(int var5 = ; var5 < var4; ++var5) {
String location = var3[var5]; try {
Resource resource = this.applicationContext.getResource(location); //
resource = resource.createRelative(viewName + ".html");
if(resource.exists()) { // 资源必须要存在, 才会返回,
return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
}
} catch (Exception var8) {
;
}
} return null; // 如果 各个静态目录下都没有找到那个 html 文件, 那么就还是 返回null, 交给白标吧 !!
}
默认情况下, 我们的静态location 也不会有 什么404.html 之类的 错误展示的文件,因为我们不知道啊。。 

那么, boot 也只有使用 白标了:

@Configuration
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration { private final SpelView defaultErrorView = new SpelView(
"<html><body><h1>Whitelabel Error Page</h1>"
+ "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
+ "<div id='created'>${timestamp}</div>"
+ "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
+ "<div>${message}</div></body></html>"); @Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
} // If the user adds @EnableWebMvc then the bean name view resolver from
// WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
@Bean
@ConditionalOnMissingBean(BeanNameViewResolver.class)
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - ); // 匹配 顺序是 。。。
return resolver;
} } WhitelabelErrorViewConfiguration 注册了 一个View, 同时 注册了BeanNameViewResolver,如果之前没有注册的话。 这样做的意义呢? 别忘了 BeanNameViewResolver 也是可以对View 进行处理的, 它的处理方式是 根据 view 的name 查找 对应的bean。 这里 defaultErrorView 也是一个bean, 其名字是 error。 整个意思就是说, 如果 发现请求是 /error , 那么 如果其他 ViewResolver 处理不了, 那么我来处理吧。 我这么处理呢? 我就 把 SpelView 渲染到 浏览器吧。 所以,我们可以看到, WhitelabelErrorView 通常是异常处理的最后一个 围墙, 因为 BeanNameViewResolver 的优先级比较低 SpelView 实现了 View , 主要就是完成了对 页面的渲染, 提供了一个 render 方法。 ErrorPageCustomizer 非非非常常常关键!!!!!!! : 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()); // 正是这里, 把 /error 这样的errorpage 注册到了 servlet 容器, 使得它异常的时候, 会转发到/error
errorPageRegistry.addErrorPages(errorPage);
} @Override
public int getOrder() {
return ;
} } 虽然,我们现在已经配置了 BasicErrorController, 没错,它默认会对 /error请求 进行处理。但是 springMVC 可没说404,4xx或500,5xx等系统异常就 转发请求给 /error 吧, springMVC是通过HandlerExceptionResolver 来处理异常的, 而且只处理异常, 不处理 之类的。 那么这个工作是谁完成的呢? 没错, 应该就是 boot 了吧! 但是, 具体呢? registerErrorPages 方法就是关键。 ErrorPage 没什么特别的,可以看做是一个简单的 javabean: private final HttpStatus status;
private final Class<? extends Throwable> exception;
private final String path; 仅仅是包含3个属性的 bean 而已。 关键是errorPageRegistry.addErrorPages(errorPage); errorPageRegistry 是关键,但是它是一个参数,是谁调用这个方法的呢? 答案在 EmbeddedServletContainerAutoConfiguration 之中。 它 为 tomcat, jetty, undertow 分别配置了 EmbeddedServletContainerFactory, 然后 : @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
registerSyntheticBeanIfMissing(registry,
"embeddedServletContainerCustomizerBeanPostProcessor",
EmbeddedServletContainerCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry,
"errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
} 其中ErrorPageRegistrarBeanPostProcessor 完成了对 errorPageRegistry 的处理 : public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof ErrorPageRegistry) { // ErrorPageCustomizer 是一个 ErrorPageRegistry, 因为这里会拦截到之前注册的 ErrorPageCustomizer bean
this.postProcessBeforeInitialization((ErrorPageRegistry)bean);
} return bean;
}
private void postProcessBeforeInitialization(ErrorPageRegistry registry) {
Iterator var2 = this.getRegistrars().iterator(); while(var2.hasNext()) {
ErrorPageRegistrar registrar = (ErrorPageRegistrar)var2.next(); // ErrorPageRegistrar 又是什么? ErrorPageCustomizer 正是它的实现!!
registrar.registerErrorPages(registry); // 这里调用之前ErrorPageCustomizer的 方法的具体实现。
} } private Collection<ErrorPageRegistrar> getRegistrars() {
if(this.registrars == null) {
this.registrars = new ArrayList(this.beanFactory.getBeansOfType(ErrorPageRegistrar.class, false, false).values());
Collections.sort(this.registrars, AnnotationAwareOrderComparator.INSTANCE);
this.registrars = Collections.unmodifiableList(this.registrars);
} return this.registrars;
} 对于EmbeddedServletContainerCustomizerBeanPostProcessor, 其实它是boot提供的另外一种方式的 错误处理 。 顾名思义, 它就是对内嵌 的 servlet 容器的 定制器: public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof ConfigurableEmbeddedServletContainer) { // 一定要注意 ConfigurableEmbeddedServletContainer 是什么?ConfigurableEmbeddedServletContainer就是 J2EE容器,另外它是 ErrorPageRegistry 的子接口
this.postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer)bean);
} return bean;
} private void postProcessBeforeInitialization(ConfigurableEmbeddedServletContainer bean) {
Iterator var2 = this.getCustomizers().iterator(); while(var2.hasNext()) {
EmbeddedServletContainerCustomizer customizer = (EmbeddedServletContainerCustomizer)var2.next(); // 强转
customizer.customize(bean); //
} } private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
if(this.customizers == null) {
this.customizers = new ArrayList(this.beanFactory.getBeansOfType(EmbeddedServletContainerCustomizer.class, false, false).values());
Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
} return this.customizers;
} 所以来说, 这里也真是够绕的了。 EmbeddedServletContainerCustomizer 是什么鬼? 它是一个接口,提供了方法:
public interface EmbeddedServletContainerCustomizer {
void customize(ConfigurableEmbeddedServletContainer var1); // 定制器,定制什么呢? 答案是 对 容器进行定制。 故这里需要一个 可config 的内嵌servlet 容器
} 因为 ConfigurableEmbeddedServletContainer 是很强大的, 故 customize 方法也变得强大了: public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry {
void setContextPath(String var1); void setDisplayName(String var1); void setPort(int var1); void setSessionTimeout(int var1); void setSessionTimeout(int var1, TimeUnit var2); void setPersistSession(boolean var1); void setSessionStoreDir(File var1); void setAddress(InetAddress var1); void setRegisterDefaultServlet(boolean var1); void setErrorPages(Set<? extends ErrorPage> var1); void setMimeMappings(MimeMappings var1); void setDocumentRoot(File var1); void setInitializers(List<? extends ServletContextInitializer> var1); void addInitializers(ServletContextInitializer... var1); void setSsl(Ssl var1); void setSslStoreProvider(SslStoreProvider var1); void setJspServlet(JspServlet var1); void setCompression(Compression var1); void setServerHeader(String var1); void setLocaleCharsetMappings(Map<Locale, Charset> var1);
} 于是,我们可以利用 EmbeddedServletContainerCustomizer, 然后间接利用 ConfigurableEmbeddedServletContainer , 做各种定制化。 另外 它还实现了ErrorPageRegistry, 于是,我们可以利用它 进行异常页面的处理: @Configuration
public class ErrorPageConfig implements EmbeddedServletContainerCustomizer { @Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(
new ErrorPage(HttpStatus.BAD_REQUEST, "/4O0.html"),
new ErrorPage(HttpStatus.UNAUTHORIZED, "/4O1.html"),
new ErrorPage(HttpStatus.NOT_FOUND, "/404/"),
new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html")
);
}
} // 参考 http://blog.csdn.net/devday/article/details/60143966 这真的是太TM 灵活了! addErrorPages 是container 完成的, 但是这里的 container 仅仅还是一个 boot 的内容, 没有实质性的 servlet容器的东西。 那么 /error 请求到底是如何被 servlet容器 处理的呢? addErrorPages 是由ConfigurableEmbeddedServletContainer的子类 AbstractConfigurableEmbeddedServletContainer 实现的, 它提供了 errorPages 属性,关键的一个 getErrorPages方法。 然而它仍然只是boot 的范畴。 我相信它没有实质作用。 注意到,前文已经提到,boot 其实已经 提供了3个 ConfigurableEmbeddedServletContainer的实现, tomcat是 TomcatEmbeddedServletContainerFactory 。 这里,真正起作用的是 TomcatEmbeddedServletContainerFactory 。 它在 configureContext (也就是初始化容器, 做配置的时候) 时, 调用getErrorPages方法。 然后: var4 = this.getErrorPages().iterator(); while(var4.hasNext()) {
ErrorPage errorPage = (ErrorPage)var4.next();
(new TomcatErrorPage(errorPage)).addToContext(context); // 看到没, 这里又是一个反向调用。 正是这里,完成了 将 errorPage 交给 servlet容器。 注意, 显然, 这里的 context 就是 servlet容器吧!
} 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("org.apache.tomcat.util.descriptor.web.ErrorPage", (ClassLoader)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); // Context 其实是提供 addErrorPage的方法的
} else {
this.callMethod(this.nativePage, "setLocation", this.location, String.class);
this.callMethod(this.nativePage, "setErrorCode", Integer.valueOf(this.errorCode), Integer.TYPE);
this.callMethod(this.nativePage, "setExceptionType", this.exceptionType, String.class);
this.callMethod(context, "addErrorPage", this.nativePage, this.nativePage.getClass());
} } 这里的 Context 仅仅还是一个接口, 实现应该是 org.apache.catalina.core.StandardContext , 这个, 显然, 就是纯正的 j2ee 的内容的吧 : public void addErrorPage(ErrorPage errorPage) {
if(errorPage == null) {
throw new IllegalArgumentException(sm.getString("standardContext.errorPage.required"));
} else {
String location = errorPage.getLocation();
if(location != null && !location.startsWith("/")) {
if(!this.isServlet22()) {
throw new IllegalArgumentException(sm.getString("standardContext.errorPage.error", new Object[]{location}));
} if(log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.errorPage.warning", new Object[]{location}));
} errorPage.setLocation("/" + location);
} String exceptionType = errorPage.getExceptionType();
HashMap var4;
if(exceptionType != null) {
var4 = this.exceptionPages;
synchronized(this.exceptionPages) {
this.exceptionPages.put(exceptionType, errorPage);
}
} else {
var4 = this.statusPages;
synchronized(this.statusPages) {
this.statusPages.put(Integer.valueOf(errorPage.getErrorCode()), errorPage);
}
} this.fireContainerEvent("addErrorPage", errorPage);
}
} 它提供了一个 HashMap 的 exceptionPages, 专门存储 错误界面。 那么 这些exceptionPages 具体又是什么时候起作用的呢? 它提供了 findErrorPage 方法 :
public ErrorPage findErrorPage(String exceptionType) {
HashMap var2 = this.exceptionPages;
synchronized(this.exceptionPages) {
return (ErrorPage)this.exceptionPages.get(exceptionType);
}
} public ErrorPage[] findErrorPages() {
...
}

那么, 你一定又会问, findErrorPage 是什么时候被调用的呢? 我通过打断点进行调试, 终于发现, 原来是在StandardHostValve 这里: public final void invoke(Request request, Response response) throws IOException, ServletException {
Context context = request.getContext();
if(context == null) {
response.sendError(, sm.getString("standardHost.noContext"));
} else {
if(request.isAsyncSupported()) {
request.setAsyncSupported(context.getPipeline().isAsyncSupported());
} boolean asyncAtStart = request.isAsync();
boolean asyncDispatching = request.isAsyncDispatching(); try {
context.bind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
if(asyncAtStart || context.fireRequestInitEvent(request.getRequest())) {
try {
if(asyncAtStart && !asyncDispatching) {
if(!response.isErrorReportRequired()) {
throw new IllegalStateException(sm.getString("standardHost.asyncStateError"));
}
} else {
context.getPipeline().getFirst().invoke(request, response); // 这里调用 valve 进行处理
}
} catch (Throwable var10) {
ExceptionUtils.handleThrowable(var10);
this.container.getLogger().error("Exception Processing " + request.getRequestURI(), var10);
if(!response.isErrorReportRequired()) {
request.setAttribute("javax.servlet.error.exception", var10);
this.throwable(request, response, var10);
}
} response.setSuspended(false);
Throwable t = (Throwable)request.getAttribute("javax.servlet.error.exception");
if(!context.getState().isAvailable()) {
return;
} if(response.isErrorReportRequired()) {
if(t != null) {
this.throwable(request, response, t); // 如果有异常, 就尝试进行异常汇报
} else {
this.status(request, response); // 如果不是, 那么就进行status 处理。 这里是关键 !!!
}
} if(!request.isAsync() && !asyncAtStart) {
context.fireRequestDestroyEvent(request.getRequest());
} return;
}
} finally {
if(ACCESS_SESSION) {
request.getSession(false);
} context.unbind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
} }
} private void status(Request request, Response response) {
int statusCode = response.getStatus();
Context context = request.getContext();
if(context != null) {
if(response.isError()) {// 如果 statusCode 是 404 等, 那么 response 就是 isError
ErrorPage errorPage = context.findErrorPage(statusCode); // 这里就是调用 StandardContext 的 findErrorPage ...
if(errorPage == null) {
errorPage = context.findErrorPage();
} if(errorPage != null && response.isErrorReportRequired()) {
response.setAppCommitted(false);
request.setAttribute("javax.servlet.error.status_code", Integer.valueOf(statusCode));
String message = response.getMessage();
if(message == null) {
message = "";
} request.setAttribute("javax.servlet.error.message", message);
request.setAttribute("org.apache.catalina.core.DISPATCHER_REQUEST_PATH", errorPage.getLocation());
request.setAttribute("org.apache.catalina.core.DISPATCHER_TYPE", DispatcherType.ERROR);
Wrapper wrapper = request.getWrapper();
if(wrapper != null) {
request.setAttribute("javax.servlet.error.servlet_name", wrapper.getName());
} request.setAttribute("javax.servlet.error.request_uri", request.getRequestURI());
if(this.custom(request, response, errorPage)) {
response.setErrorReported(); try {
response.finishResponse();
} catch (ClientAbortException var9) {
;
} catch (IOException var10) {
this.container.getLogger().warn("Exception Processing " + errorPage, var10);
}
}
} }
}
} protected void throwable(Request request, Response response, Throwable throwable) { // j2ee 内部的异常处理
Context context = request.getContext();
if(context != null) {
Throwable realError = throwable;
if(throwable instanceof ServletException) {
realError = ((ServletException)throwable).getRootCause();
if(realError == null) {
realError = throwable;
}
} if(realError instanceof ClientAbortException) {
if(log.isDebugEnabled()) {
log.debug(sm.getString("standardHost.clientAbort", new Object[]{realError.getCause().getMessage()}));
} } else {
ErrorPage errorPage = findErrorPage(context, throwable);
if(errorPage == null && realError != throwable) {
errorPage = findErrorPage(context, realError);
} if(errorPage != null) {
if(response.setErrorReported()) {
response.setAppCommitted(false);
request.setAttribute("org.apache.catalina.core.DISPATCHER_REQUEST_PATH", errorPage.getLocation());
request.setAttribute("org.apache.catalina.core.DISPATCHER_TYPE", DispatcherType.ERROR);
request.setAttribute("javax.servlet.error.status_code", Integer.valueOf());
request.setAttribute("javax.servlet.error.message", throwable.getMessage());
request.setAttribute("javax.servlet.error.exception", realError);
Wrapper wrapper = request.getWrapper();
if(wrapper != null) {
request.setAttribute("javax.servlet.error.servlet_name", wrapper.getName());
} request.setAttribute("javax.servlet.error.request_uri", request.getRequestURI());
request.setAttribute("javax.servlet.error.exception_type", realError.getClass());
if(this.custom(request, response, errorPage)) {
try {
response.finishResponse();
} catch (IOException var9) {
this.container.getLogger().warn("Exception Processing " + errorPage, var9);
}
}
}
} else {
response.setStatus();
response.setError();
this.status(request, response);
} }
}
} 运行到 status 方法, 就会 转发请求给 /error , 又回到了 boot 了! 此时的堆栈是: at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:)
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:)
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:)
at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:)
at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:) // 注意这里有个 status 调用栈
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:)
- locked <0x195c> (a org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:)
at java.lang.Thread.run(Thread.java:) 那么, 你一定又会问, 是如何设置给 response 的呢? 我看了下 StandardContextValve, 里面好像有相关内容哦, 但是呢, 答案不是 StandardContextValve, 而是spring web 框架的 ResourceHttpRequestHandler public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Resource resource = this.getResource(request);
if(resource == null) {
logger.trace("No matching resource found - returning 404");
response.sendError(); // 这里找不到资源, 于是 404
} else if(HttpMethod.OPTIONS.matches(request.getMethod())) {
response.setHeader("Allow", this.getAllowHeader());
} else {
this.checkRequest(request);
if((new ServletWebRequest(request, response)).checkNotModified(resource.lastModified())) {
logger.trace("Resource not modified - returning 304");
} else {
this.prepareResponse(response); ... }
} 此时的错误堆栈是:
at org.apache.catalina.connector.Response.sendError(Response.java:)
at org.apache.catalina.connector.Response.sendError(Response.java:)
at org.apache.catalina.connector.ResponseFacade.sendError(ResponseFacade.java:)
at javax.servlet.http.HttpServletResponseWrapper.sendError(HttpServletResponseWrapper.java:)
at com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper.sendError(WebStatFilter.java:)
at org.springframework.web.servlet.resource.ResourceHttpRequestHandler.handleRequest(ResourceHttpRequestHandler.java:)
at org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter.handle(HttpRequestHandlerAdapter.java:)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:)
at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:)
at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:)
at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:)
at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:)
- locked <0x1917> (a org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:)
at java.lang.Thread.run(Thread.java:)

那么 ResourceHttpRequestHandler ,是何时配置的,或者说何时注册? 没找到。  我感觉应该是 WebMvcAutoConfiguration 完成的

请参考 http://www.cnblogs.com/fangjian0423/p/springMVC-request-mapping.html

================================================   END =========================================================

这么大量的源码, 看完你估计也累了吧,总的来说, boot也真是够绕的了。

spring boot 错误处理之深度历险的更多相关文章

  1. spring boot错误: 找不到或无法加载主类

    一:当在eclipse启动spring boot项目时出现问题: springboot错误: 找不到或无法加载主类 解决办法: 1,通过cmd命令行,进入项目目录进行,mvn clean instal ...

  2. 16. Spring boot 错误页面

      默认效果:1).浏览器,返回一个默认的错误页面 1.1 请求头 1.2返回结果 2).如果是其他客户端,默认响应一个json数据 2.1请求头 2.2返回结果 { "timestamp& ...

  3. Spring Boot错误——SpringBoot 2.0 报错: Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

    背景 使用Spring Cloud搭建微服务,服务的注册与发现(Eureka)项目启动时报错,错误如下 *************************** APPLICATION FAILED T ...

  4. spring boot 错误处理总结

    在boot 中, 对404  和 异常 有了额外的处理. 当然,我们可以定制, 如何做呢? 1 写一个继承 ErrorController 的Controller 注意, 这里一定要继承 ErrorC ...

  5. spring boot 错误,求大神帮解决

    Exception in thread "main" java.lang.IllegalStateException: Failed to read Class-Path attr ...

  6. Spring boot 错误处理机制

    请求方式时,若不存在 浏览器出现White label Error Page 错误页面 其他客户端出现响应一个JSON格式文本包含错误码等信息 浏览器发送请求的请求头: 客户端请求头 这样就能区分来自 ...

  7. Spring boot错误处理以及定制错误页面

    如果是浏览器访问,返回错误页面 注意浏览器发送请求的请求头:  注意区别其他客户端哦比如 postman 如果是其他客户端,返回一个Json数据 原理可以参照ErrorMvcAutoConfigura ...

  8. spring boot 错误:Check your ViewResolver setup

    Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as ...

  9. Spring Boot错误errMsg: "request:ok"

    在把评论写到数据库并且动态刷新评论区的时候,有时候正常写入,有时候就会有“request:ok”的的错误出现,错误信息如下: data: {timestamp: , error: "Inte ...

随机推荐

  1. bootstrap--------bootstrap table

    bootstrap table 显示行号 <th rowspan="2" data-field="index" data-formatter=" ...

  2. Linux下查看CPU型号,内存大小,硬盘空间的命令

    1 查看CPU 1.1 查看CPU个数 # cat /proc/cpuinfo | grep "physical id" | uniq | wc -l 2 **uniq命令:删除重 ...

  3. C#生成PDF文件流

    1.设置字体 static BaseFont FontBase = BaseFont.CreateFont("C:\\WINDOWS\\FONTS\\STSONG.TTF", Ba ...

  4. Canvas名侦探柯南-canvas练习

    var canvas=document.getElementById("canvas"); var ctx=canvas.getContext("2d"); / ...

  5. 如何执行Python代码

    1.在linux系统中执行代码有两种方法 a.在脚本的当前目录下执行:python test.py b.给脚本赋予可执行权限,然后执行代码 chmod +x test.py test.py 2.在wi ...

  6. Windows 命令行解析工具(getopt)

    忘记了上次在哪里找到这个功能库,只有一个 .h 和 .c 文件,再次搜索的时候发现找不到了,结果只能在之前的代码中,两个文件提出使用,顾将这两个文件备份在这里. /* Getopt for Micro ...

  7. datePecker时间控件区间写法

    成交时间: <input type="text" onclick="WdatePicker({dateFmt:'yyyy-MM-dd',maxDate:'#F{$d ...

  8. webstrom左侧项目栏不显示文件夹问题

    在使用webstrom的时候遇到问题: 打开项目,只显示package.json和webpack.config.js其他文件夹和文件都不显示 解决办法: 1.关闭webstrom当前项目 2.找到项目 ...

  9. macbook 下hadoop伪分布式安装

    1 准备原材料 1.1  jdk 1.8.0_171(事先安装并配置环境变量HAVA_HOME,PATH) 1.2 Hadoop 2.8.3 2 免密登陆配置(否则安装过程需要不断输入密码) 2.1 ...

  10. CIFAR-10数据集读取

    参考:https://jingyan.baidu.com/article/656db9183296c7e381249cf4.html 1.使用读取方式pickle def unpickle(file) ...