背景知识
对于Java Web开发而言,客户端发起的HTTP请求处理顺序为:Servlet容器 -> Filter -> Servlet -> Interceptor -> Controller,参考:Spring拦截器HandlerInterceptor与Filter方法执行顺序探究。
如下是一个客户端HTTP请求到达Serlvet容器后的完整执行流程:
-> org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun() --------> org.apache.coyote.AbstractProtocol$ConnectionHandler.process() |-> org.apache.coyote.AbstractProcessorLight.process() |-> org.apache.coyote.http11.Http11Processor.service() |-> org.apache.catalina.connector.CoyoteAdapter.service() |-> org.apache.catalina.core.StandardEngineValve.invoke() Servlet容器-> org.apache.catalina.valves.ErrorReportValve.invoke() |-> org.apache.catalina.core.StandardHostValve.invoke() |-> org.apache.catalina.authenticator.AuthenticatorBase.invoke() |-> org.apache.catalina.core.StandardContextValve.invoke() |-> org.apache.catalina.core.StandardWrapperValve.invoke() |-> org.apache.catalina.core.ApplicationFilterChain.doFilter() |-> org.apache.catalina.core.ApplicationFilterChain.internalDoFilter() --------> 执行Filter.doFilter():如果在这里抛出了异常,在StandardWrapperValve.invoke()方法中会被捕获,并将异常对象保存到request属性中,返回到StandardHostValve.invoke()方法后检查request属性中的异常对象是否为空,如果不为空就执行RequestDispatcher.formard()访问/error路径;如果在Filter中未抛出异常,继续执行Servlet -> Interceptor -> Controller-> javax.servlet.http.HttpServlet.service() -> org.springframework.web.servlet.FrameworkServlet.service() -> org.springframework.web.servlet.FrameworkServlet.doGet()等 -> org.springframework.web.servlet.FrameworkServlet.processRequest() -> org.springframework.web.servlet.DispatcherServlet.processRequest() -> org.springframework.web.servlet.DispatcherServlet.doService() -> org.springframework.web.servlet.DispatcherServlet.doDispatch() -> org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle()-> 执行HandlerInterceptor.preHandle() -> org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle()-> org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal()-> org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod()-> org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle()-> org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest()-> org.springframework.web.method.support.InvocableHandlerMethod.doInvoke()-> 执行Controller方法-> org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue()-> org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue()-> org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters()-> org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyAdviceChain.beforeBodyWrite()-> org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice.beforeBodyWrite()<-<-<--> org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.getModelAndView()<-<-:如果在HandlerInterceptor.preHandle()方法或者Controller方法中抛出异常,会在DispatcherServlet.doDispatch()方法中被捕获,进入到DispatcherServlet.processDispatchResult()方法,最后调用全局异常拦截器进行处理-> org.springframework.web.servlet.DispatcherServlet.applyDefaultViewName()-> 执行HandlerInterceptor.postHandle()-> 执行HandlerInterceptor.afterCompletion()<-<-<-<-<-<-<-<-<-<-<-<-
<- 将响应数据写回客户端
在Filter中抛出异常
客户端发起的HTTP请求最先到达的组件就是Filter,如果在Filter.doFilter()方法中抛出了异常,在StandardWrapperValve.invoke()方法中会被捕获,并将异常对象保存到request属性中,返回到StandardHostValve.invoke()方法后检查request属性中的异常对象是否为空,如果不为空就执行RequestDispatcher.formard()访问/error路径。
// org.apache.catalina.core.StandardWrapperValveprivate void exception(Request request, Response response,Throwable exception) {// 将在Filter.doFilter()方法中抛出的异常对象保存到request属性中,属性名为RequestDispatcher.ERROR_EXCEPTION,即:"javax.servlet.error.exception"request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, exception);// 设置HTTP响应状态码response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);// 设置错误状态标志response.setError();
}
// org.apache.catalina.core.StandardHostValvepublic final void invoke(Request request, Response response)throws IOException, ServletException {// 省略其他代码...try {// 省略其他代码...// 从request属性中获取异常对象Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);// Protect against NPEs if the context was destroyed during a// long running request.if (!context.getState().isAvailable()) {return;}// Look for (and render if found) an application level error pageif (response.isErrorReportRequired()) {if (t != null) {// 如果异常不为空,throwable(request, response, t);} else {status(request, response);}}// 省略其他代码...} finally {// 省略其他代码...}
}protected void throwable(Request request, Response response,Throwable throwable) {// 省略其他代码...status(request, response);// 省略其他代码...
}// 在custom()方法中调用RequestDispatcher.forward()访问/error路径
private boolean custom(Request request, Response response,ErrorPage errorPage) {if (container.getLogger().isDebugEnabled()) {container.getLogger().debug("Processing " + errorPage);}try {// Forward control to the specified locationServletContext servletContext =request.getContext().getServletContext();RequestDispatcher rd =servletContext.getRequestDispatcher(errorPage.getLocation());if (rd == null) {container.getLogger().error(sm.getString("standardHostValue.customStatusFailed", errorPage.getLocation()));return false;}if (response.isCommitted()) {// Response is committed - including the error page is the// best we can dord.include(request.getRequest(), response.getResponse());} else {// Reset the response (keeping the real error code and message)response.resetBuffer(true);response.setContentLength(-1);// 执行RequestDispatcher.forward()方法访问错误页面(默认为/error)rd.forward(request.getRequest(), response.getResponse());// If we forward, the response is suspended againresponse.setSuspended(false);}// Indicate that we have successfully processed this custom pagereturn true;} catch (Throwable t) {ExceptionUtils.handleThrowable(t);// Report our failure to process this custom pagecontainer.getLogger().error("Exception Processing " + errorPage, t);return false;}
}
正因为如此,在Filter中抛出的异常无法被全局异常拦截器处理,如果希望在全局异常拦截器中处理Filter中抛出的异常,需要自定义org.springframework.boot.autoconfigure.web.ErrorController实现。
// 自定义org.springframework.boot.autoconfigure.web.ErrorController实现,将在Filter中抛出的异常交给全局异常拦截器处理@RequestMapping("/error")
public class DefaultErrorController implements ErrorController {@RequestMappingpublic void handleError(HttpServletRequest req) throws Throwable {Object exceptAttr = req.getAttribute("javax.servlet.error.exception");if (exceptAttr != null) {// 将异常信息抛给全局异常拦截器处理throw (Throwable) exceptAttr;}}
}
在HandlerInterceptor或Controller中抛出异常
如果在HandlerInterceptor.preHandle()方法或者Controller方法中抛出异常,会在DispatcherServlet.doDispatch()方法中被捕获,进入到DispatcherServlet.processDispatchResult()方法,最后调用全局异常拦截器进行处理。
// org.springframework.web.servlet.DispatcherServlet// 省略其他代码.../*** Handle the result of handler selection and handler invocation, which is* either a ModelAndView or an Exception to be resolved to a ModelAndView.*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {boolean errorView = false;// 如果异常对象不为空if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// 省略其他代码...if (mappedHandler != null) {// 不论HandlerInterceptor.preHandle()方法或Controller方法是否抛出异常,HandlerInterceptor.afterCompletion()方法总是会被调用// 特别注意:如果HandlerInterceptor.preHandle()方法未抛出异常,而是明确返回了false,HandlerInterceptor.afterCompletion()方法不会被调用mappedHandler.triggerAfterCompletion(request, response, null);}
}protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception {// Check registered HandlerExceptionResolvers...ModelAndView exMv = null;// 在这里通过全局异常拦截器处理在Controller或HandlerInterceptor.preHandle()方法中抛出的异常for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}// 省略其他代码...
}
解读完毕。