SpringMVC 在一个请求中扮演的角色

446 阅读9分钟

上篇文章分析了 Servlet 容器在 Tomcat 中的启动过程,这篇文章记录一下一个请求在 SpringMVC 中的处理流程。

概览

一个请求从客户端发出,到达 Servlet 被程序逻辑处理需要经过以下过程:

  1. 用户从客户端发出请求,可以是浏览器或者其他客户端软件。
  2. 请求到达 Web 服务器,首先接收到该请求的是 Tomcat 中的 HTTP 服务器。
  3. Tomcat 中的 HTTP 服务器中的连接器将首先对请求进行处理。
  4. 连接器由三部分组成,从请求的执行顺序而言依次是 Endpoint,Processor,Adapter。连接器可以认为是对传输层的数据进行处理。
    1. Endpoint 又由两部分组成,一部分是 Accpetor,负责接收过来的 Socket 连接。另一部分是 SocketProcessor,Tomcat 收到 Socket 请求之后,需要创建一个新的 Socket 连接,用于跟进该请求的后续动作。(该步与 Socket 编程一致)并将新创建的 Socket 发送至 Processor。 Acceptor 到 SocketProcessor 中间存在一个线程池,Acceptor 收到 Socket 连接后,会生成一个 SocketProcessor 任务交由线程池进行处理。
    2. Processor 收到 Socket 连接后,需要对其进行处理,将 Socket 中携带的数据解析成 Tomcat 中定义的 Request。
    3. Adapter 收到 Tomcat 定义的 Request 之后,会将 Request 封装成 ServletRequest, 相当于在原来的 Request 上做了一个适配。
  5. Servlet 请求到达 Servlet 容器之后,因为 Servlet 容器是分层结构的,从外层到内层依次是 Engine -> Host -> Context -> Wrapper(Servlet),各层之间是以责任链的模式相互关联着,每一层都可以对请求进行处理(通过配置 Value)。最后一个 Wrapper 会调用 Servlet 的 service 方法,从而最终请求进入到 DispatcherServlet 中被处理。

下面通过源码来分析一个请求的全部流程。

Pipeline-Value 处理

前面提到过,Adapter 的作用是将收到的 Tomcat 中定义的 Request 适配为 HttpServletRquest。在 CoyoteAdapter#service(Request, Response) 方法中就做了这件事。

    @Override
    public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
            throws Exception {

        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);
        // ...
        try {
            // Parse and set Catalina and configuration specific
            // request parameters
            postParseSuccess = postParseRequest(req, request, res, response);
            if (postParseSuccess) {
                //check valves if we support async
                request.setAsyncSupported(
                        connector.getService().getContainer().getPipeline().isAsyncSupported());
                // Calling the container
                // 调用容器
                connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);
            }
            // ...
        } 
    }

将请求处理成 HttpServletRquest 类型后,进入责任链调用逻辑。 首先获取最外层容器,也就是 Engine,具体类型为 StandardEngine。 并获取 StandardEngine 持有的 Pipeline,具体类型为 StandardPipeline。 随后调用当前 StandardPipeline 中的第一个 Value。Value 表示当前 Container(Container 是一个接口,像 Engine,Host,Context,Wrapper 都实现了该接口,所以都可以被称为 Container) 的一个处理点。可以对当前请求进行处理。获取到 Value 之后调用其 invoke 方法,对请求进行处理。

public final void invoke(Request request, Response response)
    throws IOException, ServletException {
    // Select the Host to be used for this Request
    Host host = request.getHost();
    if (host == null) {
        // HTTP 0.9 or HTTP 1.0 request without a host when no default host
        // is defined. This is handled by the CoyoteAdapter.
        return;
    }
    if (request.isAsyncSupported()) {
        request.setAsyncSupported(host.getPipeline().isAsyncSupported());
    }
    // Ask this Host to process this request
    host.getPipeline().getFirst().invoke(request, response);
}

可以看到 StandardEngineValue#invoke 方法没做什么,仅仅是将调用传递了下去。传递下去之后同样还是先获取 Host Pipeline 的第一个 Value。这里需要注意的是 Host Pipeline 中的第一个 Value 并不是 StandardHostValue,而是 ErrorReportValve。下面就是 ErrorReportValve 的 invoke 方法。

public void invoke(Request request, Response response) throws IOException, ServletException {
    // Perform the request
    getNext().invoke(request, response);
    // ...
}

首先这里可以看到,invoke 方法进来立马调用了 getNext().invoke(), 这段逻辑调用到了真正的 StandardHostValue 的 invoke 方法。那么 ErrorReportValve 的作用是什么呢?

其实,ErrorReportValve 的作用是当请求发生错误的时候,比如 404,页面上会出现我们熟悉的 404 页面。ErrorReportValve 的作用对错误页面的处理。在 ErrorReportValue#report 方法中,输出了错误页面。

回到 StandardHostValue 处理方法上来。

    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
        // Select the Context to be used for this Request
        Context context = request.getContext();
        // ...
        if (!response.isErrorReportRequired()) {
            context.getPipeline().getFirst().invoke(request, response);
        }
        // ...
    }

StandardHostValue 的 invoke 方法也是直接调用了 Context 的 Pipeline 中的第一个 Value 进行处理。这里需要注意的是 Context 的 Pipeline 中的第一个 Value 并不是 StandardContextValue, 而是 AuthenticatorBase。因此调用的方法是 AuthenticatorBase#invoke 方法。 AuthenticatorBase 是一个安全认证的 Value,用户需要通过安全认证之后才可以继续后续流程。安全认证通过之后才调用到了 StandardContextValue#invoke 方法。

public final void invoke(Request request, Response response)
    throws IOException, ServletException {
    // Disallow any direct access to resources under WEB-INF or META-INF
    // 不允许任何直接访问 /META-INF/ /META-INF /WEB-INF/ /WEB-INF 的请求
    MessageBytes requestPathMB = request.getRequestPathMB();
    if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
            || (requestPathMB.equalsIgnoreCase("/META-INF"))
            || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
            || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }
    // Select the Wrapper to be used for this Request
    // 获取 Wrapper
    Wrapper wrapper = request.getWrapper();
    if (wrapper == null || wrapper.isUnavailable()) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }
    // Acknowledge the request
    try {
        response.sendAcknowledgement();
    } catch (IOException ioe) {
        container.getLogger().error(sm.getString(
                "standardContextValve.acknowledgeException"), ioe);
        request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        return;
    }
    if (request.isAsyncSupported()) {
        request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
    }
    wrapper.getPipeline().getFirst().invoke(request, response);
}

这里有个注意点就是,在 StandardContextValue 处理过程中,会返回一个当前 request 的 ack。 随后继续调用 Wrapper 的 Pipeline-Value,也就是调用 StandardWrapperValue#invoke 方法。

public final void invoke(Request request, Response response)
    throws IOException, ServletException {
    // ...
    // 首先获取当前 StandardWrapperValue 持有的 Container
    StandardWrapper wrapper = (StandardWrapper) getContainer();
    Servlet servlet = null;
    Context context = (Context) wrapper.getParent();
    // ...
    // Allocate a servlet instance to process this request
    if (!unavailable) {
        servlet = wrapper.allocate();
    }
    // ...
    // Create the filter chain for this request
    ApplicationFilterChain filterChain =
            ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

    // Call the filter chain for this request
    // NOTE: This also calls the servlet's service() method
    Container container = this.container;
    // ...
    filterChain.doFilter(request.getRequest(), response.getResponse());
    // ...
    } finally {
        // Release the filter chain (if any) for this request
        if (filterChain != null) {
            filterChain.release();
        }

        // Deallocate the allocated servlet instance
        try {
            if (servlet != null) {
                wrapper.deallocate(servlet);
            }
        } catch (Throwable e) {
            ExceptionUtils.handleThrowable(e);
            container.getLogger().error(sm.getString("standardWrapper.deallocateException",
                             wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }

        // If this servlet has been marked permanently unavailable,
        // unload it and release this instance
        try {
            if ((servlet != null) &&
                (wrapper.getAvailable() == Long.MAX_VALUE)) {
                wrapper.unload();
            }
        } catch (Throwable e) {
            ExceptionUtils.handleThrowable(e);
            container.getLogger().error(sm.getString("standardWrapper.unloadException",
                             wrapper.getName()), e);
            if (throwable == null) {
                exception(request, response, e);
            }
        }
        long t2=System.currentTimeMillis();

        long time=t2-t1;
        processingTime += time;
        if( time > maxTime) maxTime=time;
        if( time < minTime) minTime=time;
    }
}

StandardWrapperValue#invoke 方法可算是以上所有 invoke 方法中最冗长的方法了。

  1. 首先获取当前 StandardWrapperValue 中持有的 Container, 其实就是 Wrapper,具体类型为 StandardWrapper。
  2. 调用 servlet = wrapper.allocate(),为当前请求分配一个 Servlet。 Standard#allocate 方法后续可以好好看一下。
  3. 为当前的请求创建 filterChain。默认会创建四个 Filter,characterEncodingFiler,formContentFilter,requestContextFilter,Tomcat WebSocket (JSR356) Filter。并且会过滤所有的请求。
  4. filterChain 执行 doFilter 方法。一般会调用到 ApplicationFilterChain#internalDoFilter 方法。执行 Chain 上的所有的 Filter,对 request 进行处理。
  5. 最后一个 filter 执行完之后,会调用上面分配的 servlet 的 service 方法(HttpServlet#service(ServletRequest, ServletResponse))。

StandardWrapperValue#invoke 方法就先分析到这里。下面看一下中间提到过的 StandardWrapper#allocate 方法,该方法为当前请求分配一个 Servlet。

public Servlet allocate() throws ServletException {

    // ...
    boolean newInstance = false;
    // If not SingleThreadedModel, return the same instance every time
    if (!singleThreadModel) {
        // Load and initialize our instance if necessary
        if (instance == null || !instanceInitialized) {
            synchronized (this) {
                if (instance == null) {
                    try {
                        if (log.isDebugEnabled()) {
                            log.debug("Allocating non-STM instance");
                        }
                        // Note: We don't know if the Servlet implements
                        // SingleThreadModel until we have loaded it.
                        // 加载 Servlet
                        instance = loadServlet();
                        newInstance = true;
                        if (!singleThreadModel) {
                            // For non-STM, increment here to prevent a race
                            // condition with unload. Bug 43683, test case
                            // #3
                            countAllocated.incrementAndGet();
                        }
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        ExceptionUtils.handleThrowable(e);
                        throw new ServletException(sm.getString("standardWrapper.allocate"), e);
                    }
                }
                if (!instanceInitialized) {
                    initServlet(instance);
                }
            }
        }
        if (singleThreadModel) {
            if (newInstance) {
                // Have to do this outside of the sync above to prevent a
                // possible deadlock
                synchronized (instancePool) {
                    instancePool.push(instance);
                    nInstances++;
                }
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("  Returning non-STM instance");
            }
            // For new instances, count will have been incremented at the
            // time of creation
            if (!newInstance) {
                countAllocated.incrementAndGet();
            }
            return instance;
        }
    }
    synchronized (instancePool) {
        while (countAllocated.get() >= nInstances) {
            // Allocate a new instance if possible, or else wait
            if (nInstances < maxInstances) {
                try {
                    instancePool.push(loadServlet());
                    nInstances++;
                } catch (ServletException e) {
                    throw e;
                } catch (Throwable e) {
                    ExceptionUtils.handleThrowable(e);
                    throw new ServletException(sm.getString("standardWrapper.allocate"), e);
                }
            } else {
                try {
                    instancePool.wait();
                } catch (InterruptedException e) {
                    // Ignore
                }
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("  Returning allocated STM instance");
        }
        countAllocated.incrementAndGet();
        return instancePool.pop();
    }
}
  1. 在 StandardWrapper#allocate 方法中,首先判断当前 Servlet 是否实现了 SingleThreadModel 接口,一般都不会实现该接口。并且该 SingleThreadModel 接口已经标明被废弃。所以一般不会去新创建 servlet,而是直接使用已经创建了的 Servlet。
  2. 随后会判断是否 instance 已经初始化,也就是 DispatcherServlet 是否已经加载。这个根据配置中的 load_on_startup 参数决定,如果 load_on_startup 为负数,则在 Servlet 容器加载的是并没有 load Servlet,那么这会儿会对其进行加载。

Pipeline-Value 小结

以上过程就是 Pipeline-Value 的全部处理过程,下面要进入到 Spring 对 Servlet 的处理了。

总结一下整个 Pipeline-Value 的处理过程: StandardEngineValue -> ErrorReportValve -> StandardHostValue -> AuthenticatorBase -> StandardContextValue -> StandardWrapperValue 其中 StandardWrapperValue 处理的事务最多。包括为当前请求分配 servlet,为当前请求创建 filterChain,并执行 filter,最后调用 servlet 的 service 方法将请求交给 servlet 进行处理。

SpringMVC 对请求的处理

在 ApplicationFilterChain 执行完 filter 之后,调用到了 Servlet 的 service 方法,正式由 Servlet 来对请求进行处理。

public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException {

    HttpServletRequest  request;
    HttpServletResponse response;

    try {
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    } catch (ClassCastException e) {
        throw new ServletException(lStrings.getString("http.non_http"));
    }
    service(request, response);
}

在 HttpServlet#service(ServletRequest, ServletResponse) 方法中,将 ServletRequest 转为 HttpServletRequest,并调用 FrameworkServlet#service(HttpServletRequest, HttpServletResponse)。

protected void service(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
	HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
	if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
		processRequest(request, response);
	}
	else {
		super.service(request, response);
	}
}

随后调用到 HttpService#service(HttpServletRequest, HttpServletResponse) 方法,该方法对不同的请求方法调用对应 doXXX 方法,这些方法最终会调回到 FrameworkServlet#processRequest(HttpServletRequest, HttpServletResponse) 方法。FrameworkServlet#processRequest(HttpServletRequest, HttpServletResponse) 方法会调用到 DispatcherServlet#doService 方法。

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	logRequest(request);
	// Keep a snapshot of the request attributes in case of an include,
	// to be able to restore the original attributes after the include.
	Map<String, Object> attributesSnapshot = null;
	if (WebUtils.isIncludeRequest(request)) {
		attributesSnapshot = new HashMap<>();
		Enumeration<?> attrNames = request.getAttributeNames();
		while (attrNames.hasMoreElements()) {
			String attrName = (String) attrNames.nextElement();
			if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
				attributesSnapshot.put(attrName, request.getAttribute(attrName));
			}
		}
	}
	// Make framework objects available to handlers and view objects.
	request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
	request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
	request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
	request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
	if (this.flashMapManager != null) {
		FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
		if (inputFlashMap != null) {
			request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
		}
		request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
		request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
	}
	try {
		doDispatch(request, response);
	}
    // ....
}

在 doService 中做了一下事情:

  1. 设置属性,包括 context、 localeResolver 以及 themeResolver。
  2. 通过 FlashMapManager 尝试获取 flashMap,如果 flashMap 不为空,设置 request 的 INPUT_FLASH_MAP_ATTRIBUTE 属性。并设置 OUTPUT_FLASH_MAP_ATTRIBUTE 以及 FLASH_MAP_MANAGER_ATTRIBUTE 属性。
  3. 调用 DispatcherServlet#doDispatcher 方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	boolean multipartRequestParsed = false;
	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	try {
		ModelAndView mv = null;
		Exception dispatchException = null;
		try {
			processedRequest = checkMultipart(request);
			multipartRequestParsed = (processedRequest != request);
			// Determine handler for the current request.
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null) {
				noHandlerFound(processedRequest, response);
				return;
			}
			// Determine handler adapter for the current request.
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
			// Process last-modified header, if supported by the handler.
			String method = request.getMethod();
			boolean isGet = "GET".equals(method);
			if (isGet || "HEAD".equals(method)) {
				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
			}
			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}
			// Actually invoke the handler.
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			if (asyncManager.isConcurrentHandlingStarted()) {
				return;
			}
			applyDefaultViewName(processedRequest, mv);
			mappedHandler.applyPostHandle(processedRequest, response, mv);
		}
		catch (Exception ex) {
			dispatchException = ex;
		}
		catch (Throwable err) {
			// As of 4.3, we're processing Errors thrown from handler methods as well,
			// making them available for @ExceptionHandler methods and other scenarios.
			dispatchException = new NestedServletException("Handler dispatch failed", err);
		}
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
	catch (Exception ex) {
		triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
	}
	catch (Throwable err) {
		triggerAfterCompletion(processedRequest, response, mappedHandler,
				new NestedServletException("Handler processing failed", err));
	}
	finally {
		if (asyncManager.isConcurrentHandlingStarted()) {
			// Instead of postHandle and afterCompletion
			if (mappedHandler != null) {
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
			}
		}
		else {
			// Clean up any resources used by a multipart request.
			if (multipartRequestParsed) {
				cleanupMultipart(processedRequest);
			}
		}
	}
}

doDispatcher 方法中,做了以下几件事情:

  1. 检查 request 是否为上传文件操作。
  2. 通过 getHandler 方法获取当前 request 的 handler。这个过程需要通过 HandlerMapping 来进行匹配。AbstractHandlerMapping#getHandlerInternal 方法最终会找到匹配的 HandlerMethod。
  3. 找到 HandlerMethod 后还没完,根据找到的 HandlerMethod 会创建一个 HandlerExecutionChain。 HandlerExecutionChain 一般为一个 handler 和一个 interceptors 列表。
  4. HandlerExecutionChain 中会默认放入两个 interceptor, ConversionServiceExposingInterceptor 和 ResourceUrlProviderExposingInterceptor。至此 HandlerExecutionChain 创建完毕。
  5. 根据 HandlerExecutionChain 中的 handler 去寻找匹配的 HandlerAdapter。 同样遍历当前 DispatcherServlet 所持有的所有的 HandlerAdapter。 通过 HandlerAdapter#support 方法判断。
  6. 执行 HandlerExecutionChain#applyPreHandle 方法,执行 handle 之前的 interceptor 逻辑。遍历 HandlerExecutionChain 中所有的 interceptors,执行 preHandle 方法。applyPreHandle 如果返回 false,则直接返回,不对请求进行处理。
  7. 调用 HandlerAdapter#handler 方法对请求进行处理。该方法后续就对请求进行了处理。具体的逻辑就不再此篇文章中列出了,在后续分别单独解析组件的时候再分析。
  8. 调用 DispatcherServlet#applyDefaultViewName 方法,判断是否需要对 view name 进行转换。如果需要转换的话,则要使用九大组件中的 RequestToViewNameTranslator 进行转换。
  9. 调用 HandlerExecutionChain 中的 interceptor 的 postHandle 进行后置处理。
  10. 调用 DispatcherServlet#processDispatchResult 方法,对结果进行处理。如果请求处理过程中出现异常,都会在这里进行处理。异常会被 DispatcherServlet#processHandlerException 方法进行处理,这里用到了九大组件中的 HandlerExceptionResolver。
  11. 上述调用中如果没有出现异常,那么走正常逻辑,调用 DispatcherServlet#render 方法进行渲染。渲染过程中通过 LocaleResolver 设置 response 的 locale。并且会根据 ModelAndView 中的 viewName 通过 ViewResolver 来解析出 View。然后调用 View#render 方法进行渲染。
  12. 视图渲染完之后,会触发 HandlerExecutionChain 中 interceptor 的 afterCompletion 方法,执行完成处理之后的逻辑。

可以看到,DispatcherServlet#doService 方法以及 DispatcherServlet#doDispatcher 方法执行过程中用到了全部九大组件。至此,一个请求在 SpringMVC 中的处理也就完成了。后续将对 SpringMVC 中的九大组件进行分析。

推荐阅读