SpringMVC详解(二):DispatcherServlet

358 阅读3分钟

初始化时机

SpringBoot的嵌入式Tomcat,将Spring与Tomcat做了深度的融合,使得初始化流程与外置Tomcat的Spring应用程序有了较大不同。

ServletWebServerApplicationContext

ServletWebServerApplicationContext类就是负责创建并启动嵌入式的Tomcat服务器。

protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();
    } catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

private void createWebServer() {
    ServletWebServerFactory factory = getWebServerFactory();
    this.webServer = factory.getWebServer(getSelfInitializer());
}

protected ServletWebServerFactory getWebServerFactory() {
    return (ServletWebServerFactory) getBeanFactory().getBean(AbstractServletWebServerFactory.BEAN_NAME);
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
    // 这里要加几行注释,写明这个类是干什么的。
    prepareWebApplicationContext(servletContext);
    registerApplicationScope(servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
    }
}

protected void finishRefresh() {
    super.finishRefresh();
    WebServer webServer = startWebServer();
    if (webServer != null) {
            publishEvent(new ServletWebServerInitializedEvent(webServer, this));
    }
}

自动配置机制跳出懒加载

在传统的外置Tomcat的Spring Web程序中,可以通过配置load-on-startup控制Servlet的初始化时机。

<web-app>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-servlet.xml</param-value>
        </init-param>
        <load-on-startup>-1</load-on-startup> <!-- 默认懒加载,改为1则立即初始化 -->
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

在SpringBoot嵌入式Tomcat中,spring-boot-autoconfigure包下的META-INF/spring.factories中标记的自动配置类DispatcherServletAutoConfiguration负责定义DispatcherServlet。 image.png 就在ServletWebServerApplicationContext的getWebServerFactory()方法中,getBeanFactory().getBean(AbstractServletWebServerFactory.BEAN_NAME)会触发级联加载,其中包括由DispatcherServletAutoConfiguration定义的Bean。

当然上述流程是非常复杂的,里面有很多细节都还没有理清楚,应该可以从Tomcat启动的视角,后续再回头来看这个问题。

请求处理

那么,上面的流程指出,在SpringBoot程序启动完成时,DispatcherServlet就已经初始化好了,已经处于可以随时处理请求的状态了。

处理流程

1、接收分发

请求首先由 Tomcat 的 Connector 接收,并传递给合适的 Context 和 Wrapper 进行处理。对于 Spring Boot 应用,DispatcherServlet 一般被映射到根路径 (/),所以几乎所有请求都会通过 DispatcherServlet。

当然这部分应该算是Tomcat部分的内容,先按下不表。

2、请求接收

和其他HttpServlet一样,DispatcherServlet接收Http请求的方法都是doService():

void doService(HttpServletRequest request, HttpServletResponse response);

而在doService()方法中,会调用doDispatch()方法真正处理请求:

void doDispatch(HttpServletRequest request, HttpServletResponse response);

doDispatch()处理时序如下。其中HandlerExecutionChain用到责任链模式,拦截器数组中每个拦截器都可以处理请求的某一阶段,避免请求发送者和接收者之间的耦合;HandlerAdapter用到适配器模式,用于处理不同类型的Handler,可以统一的方式处理不同类型请求。

image.png

3、路由Controller

在getHandler()方法中,通过debug走到getHandlerInternal方法中。这里,lookupPath就是url,lookupHandlerMethod()方法会获取到url路径对应后端的Controller处理方法。

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    request.setAttribute(LOOKUP_PATH, lookupPath);
    this.mappingRegistry.acquireReadLock();
    try {
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        this.mappingRegistry.releaseReadLock();
    }
}

4、获取适配器

经过getHandlerAdapter()方法,会获取到RequestMappingHandlerAdapter,而这个适配器是请求处理的核心。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
                    "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

5、请求处理

请求处理是在RequestMappingHandlerAdapter中执行的,一路debug可以看到:

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    // ...
    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    // ...
}

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
    Object... providedArgs) throws Exception {
    // 这里就获取到了Controller方法的返回值了
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    // ...
}

6、返回值

@ResponseBody 注解的作用是指示 Spring MVC 将控制器的方法返回值直接写入 HTTP 响应体,而不是解析为视图名进行跳转。

HandlerMethodReturnValueHandlerComposite 按顺序调用合适的 HandlerMethodReturnValueHandler, 其中RequestResponseBodyMethodProcessor 检测 @ResponseBody 注解,并处理返回值。 消息转换:

HttpMessageConverter 接口及其实现类: 将返回值转换为指定格式并写入响应体。