您的位置:首页 > 编程语言 > Java开发

spring boot 错误处理之深度历险

2017-12-01 21:48 447 查看
今天终于把 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通常是 404 ,500 之类的 错误的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 = 0; 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 - 10); //  匹配 顺序是 。。。
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 0;
}

}

虽然,我们现在已经配置了 BasicErrorController, 没错,它默认会对 /error请求 进行处理。但是 springMVC 可没说404,4xx或500,5xx等系统异常就 转发请求给  /error 吧, springMVC是通过HandlerExceptionResolver 来处理异常的, 而且只处理异常, 不处理 404 之类的。 那么这个工作是谁完成的呢?  没错, 应该就是 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(500, 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(0);
}

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(500));
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(500);
response.setError();
this.status(request, response);
}

}
}
}

运行到 status 方法, 就会 转发请求给 /error , 又回到了 boot 了! 此时的堆栈是:

at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:869)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:728)
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:469)
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:392)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:311)
at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:395)
at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:254)        // 注意这里有个 status 调用栈
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:177)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
- locked <0x195c> (a org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)

那么, 你一定又会问,404 是如何设置给 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);         // 这里找不到资源, 于是 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:1313)
at org.apache.catalina.connector.Response.sendError(Response.java:1284)
at org.apache.catalina.connector.ResponseFacade.sendError(ResponseFacade.java:478)
at javax.servlet.http.HttpServletResponseWrapper.sendError(HttpServletResponseWrapper.java:129)
at com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper.sendError(WebStatFilter.java:342)
at org.springframework.web.servlet.resource.ResourceHttpRequestHandler.handleRequest(ResourceHttpRequestHandler.java:332)
at org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter.handle(HttpRequestHandlerAdapter.java:51)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:110)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:108)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
- locked <0x1917> (a org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)


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

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

这么大量的源码, 看完你估计也累了吧,总的来说, boot也真是够绕的了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: