Spring MVC SpringBoot源码解析:异常请求处理流程;发送/error并返回error视图的详细原理
引言
一个错误请求的处理流程主要是这样的:
-
当发送一个错误请求时,spring mvc会尝试处理这个请求,比如尝试去寻找静态资源等等;
-
如果处理失败,会将错误信息保存,然后重新发送一个/error请求;
-
/error请求会被errorController处理,返回一个error视图;
本文主要通过源码分析异常请求处理的整个流程,对于springboot提供的BasicErrorController等解析error的组件不做过多描述;
SpringBoot默认处理规则
默认情况下,Spring Boot提供 “/error” 处理所有错误的映射(BasicErrorController);
对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息;
对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据;
不论error html页面还是error json,能够得到的属性是一样的,有:
timestamp, status, error, exception, message, errors, trace, path
*异常请求处理流程
异常请求处理的流程主要分成四步:
- 解析错误请求被作为异常抛出
- 尝试处理错误请求
- 如果处理失败,会给底层response发送错误信息;然后再重新发送一个/error请求
- /error请求被ErrorController处理(SpringBoot帮我们自动配置了BasicErrorController)
步骤一:将错误请求作为异常抛出
当发送请求时,DispatcherServlet的doDispatch方法会寻找合适的Handler,然后通过Handler去找合适的Adapter去最终处理这个请求;
当发送错误请求时,寻找Handler过程就会抛出异常并被catch捕获;
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
...
捕获的异常会调用processDispatchResult尝试处理异常;
例:当发送一个405请求:Method Not Allowed,
部分栈轨迹:
handleNoMatch:250, RequestMappingInfoHandlerMapping (org.springframework.web.servlet.mvc.method)
lookupHandlerMethod:417, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
getHandlerInternal:364, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
getHandlerInternal:123, RequestMappingInfoHandlerMapping (org.springframework.web.servlet.mvc.method)
getHandlerInternal:66, RequestMappingInfoHandlerMapping (org.springframework.web.servlet.mvc.method)
getHandler:491, AbstractHandlerMapping (org.springframework.web.servlet.handler)
getHandler:1255, DispatcherServlet (org.springframework.web.servlet)
doDispatch:1037, DispatcherServlet (org.springframework.web.servlet)
doService:961, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:626, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:733, HttpServlet (javax.servlet.http)
最终handleNoMatch方法抛出HttpRequestMethodNotSupportedException:
@Override
protected HandlerMethod handleNoMatch(
Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) throws ServletException {
PartialMatchHelper helper = new PartialMatchHelper(infos, request);
if (helper.isEmpty()) {
return null;
}
if (helper.hasMethodsMismatch()) {
Set<String> methods = helper.getAllowedMethods();
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
HttpOptionsHandler handler = new HttpOptionsHandler(methods);
return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
}
throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);
}
步骤二:尝试处理异常
抛出的异常都会被DispatcherServlet.doDispatch的catch捕获到,然后带着这个异常信息调用processDispatchResult方法:
这个方法首先会尝试处理异常信息去获取ModelAndView对象:
该方法的主要逻辑:
- 寻找能够处理异常的对象:如果是ModelAndView定义异常,会返回一个特殊的视图(表明视图定义异常);否则,说明是handler的异常,会调用processHandlerException处理(具体看下面该方法的处理流程);
- 如果最终成功处理了异常,会得到ModelAndView对象,然后将视图渲染,处理完成;否则抛出异常。
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView(