spring boot 错误处理之深度历险

 

今天终于把 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通常是 404500 之类的 错误的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也真是够绕的了。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值