Spring 原始碼解析一:SpringMVC 的載入機制

senntyou發表於2021-10-09

Spring 原始碼解析一:SpringMVC 的載入機制

1. spring-framework 包含的模組

在解析 SpringMVC 的載入機制之前,先來看看官方 spring-framework 包含有哪些模組,各有什麼用。

spring-framework 官方倉庫

  • spring-jclspring 框架的通用日誌處理
  • spring-corespring 框架的核心機制模組,包括 Java 位元組碼的操作處理與動態生成、依賴注入機制(也叫控制反轉)、工具類庫、
    註解操作、編碼處理與資料轉換、資源載入與處理、環境載入機制等
  • spring-beansspring bean 的定義、載入、解析、編輯等
  • spring-contextspring 框架的上下文環境,包括應用快取、應用事件、應用配置、核心註解定義與處理、資源載入、非同步與定時任務、資料驗證與格式化等
  • spring-aop:面向切面程式設計的封裝與處理
  • spring-aspects:使用 AspectJ 作為底層實現的面向切面程式設計
  • spring-tx:事務的封裝與處理
  • spring-jdbc:資料庫連結的封裝與處理
  • spring-context-indexer:對註解 @Indexed 的支援
  • spring-context-support:對一些第三方庫的可選支援,如 ehcache, javamail, quartz, freemarker
  • spring-oxm:對 O/X Mapper 的封裝
  • spring-messaging:對 http, rsocket, simp 等訊息傳遞協議的封裝
  • spring-jms:對 JMS(Java 訊息服務) 的封裝
  • spring-expressionSpring Expression Language (SpEL) Spring 表示式語言的實現
  • spring-r2dbc:對 R2DBC 的封裝
  • spring-orm:對 JPAhibernate 的封裝
  • spring-web:提供了 Web 框架的基礎結構與技術,如 Http 的呼叫、過濾、處理等
  • spring-webmvc:Web MVC 架構的實現,包括 Servlet 容器初始化、路由對映、檢視渲染、響應處理等
  • spring-websocket:對 WebSocket 的支援
  • spring-webflux:Reactive Web 框架的實現,與 spring-webmvc 相對

SpringMVC 框架的核心模組主要是:spring-corespring-beansspring-contextspring-webspring-webmvc,後面也主要從這幾個模組來分析。

1.1. spring-core

spring-core 的核心功能有幾點需要在這裡簡單介紹一下:

  1. spring-core 有強大的 Java 位元組碼操作處理功能與動態生成功能,這是面向切面程式設計、資料型別轉換、SpEL 表示式等功能的基礎
  2. spring-core 提供了依賴注入機制,這是 spring bean 載入的基礎,也是我們可以使用 @Autowired 自動裝載物件等功能的底層機制
  3. spring-core 提供了環境載入的機制,所以我們可以使用 application-dev.yml, application-test.yml, application-prod.yml, ...
    來根據環境載入不同的配置
  4. spring-core 提供了一個類似 Java SPI 的的擴充套件機制,可以自動例項化其他包指定的類,spring-boot, spring-cloud 都依賴這個機制自動載入資源。
    META-INF/spring.factories 檔案中定義需要自動載入的類,詳細介紹可以參考 Spring Factories

1.2. spring-beans

Spring bean 的載入與擴充套件機制有幾點需要在這裡簡單介紹一下:

  1. Spring bean 的定義主要是兩種:基於註解的定義、基於 XML 檔案的定義
  2. spring-beans 提供了基於 XML 配置的、第三方對 bean 的自定義擴充套件機制,主要是在 META-INF/spring.handlers, META-INF/spring.schemas 檔案中定義需要擴充套件的標籤,比如 <dubbo:application name="name"/>, <dubbo:registry address="address"/>
  3. 基於註解的自定義擴充套件,需要依賴 spring-boot 的擴充套件載入機制

1.3. spring-context

spring-context 是應用的核心處理部分,包括:

  • 應用快取
  • 應用事件
  • 應用配置
  • 核心註解定義與處理
  • 資源載入
  • 非同步與定時任務
  • 資料驗證與格式化

等,@ComponentScan, @Profile, @Conditional, @Bean, @Async, @Controller, @Service, @Component, @Validated 等這類框架核心註解便是在這裡定義的。

1.4. spring-web

spring-web 是 Http 的核心處理部分,主要包含:

  • 核心 Http 請求與響應處理(包括 Cookie、快取、多媒體等)
  • Http 請求與響應編解碼與轉換(包括 Json、XML、ProtoBuf 等)
  • Reactive Web 框架基礎處理
  • 呼叫客戶端(如 RestTemplate
  • Servlet 上下文環境
  • 請求過濾器
  • Multipart 檔案上傳處理

等,@RequestMapping, @RequestParam, @PathVariable, @ResponseBody, @RestController 等這類 Web 核心註解便是在這裡定義的。

1.5. spring-webmvc

spring-webmvc 依賴於 spring-web,主要功能包括:

  • Servlet 容器初始化
  • 路由對映
  • 檢視渲染
  • 響應處理

等,如果不使用 Spring MVC ,但想要藉助其它 Spring 支援的 web 相關技術的優勢,那麼只需依賴 spring-web,如 spring-webflux

1.6. spring-webflux

spring-webfluxspring-webmvc 相對應,webmvc 是同步阻塞框架,而 webflux 是非同步非阻塞框架,是 Spring Framework 5.0 中引入的新的響應式 web 框架。

參考:Spring WebFlux 入門Spring WebFlux :: Spring Docs

2. 一個簡單的 spring-webmvc 專案配置

WEB-INF/web.xml 檔案中如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

  <display-name>springMVC</display-name>

  <!-- 部署 DispatcherServlet -->
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc-servlet.xml</param-value>
    </init-param>
    <!-- 容器再啟動時立即載入servlet -->
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <!-- 處理所有URL -->
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  <!-- 定義應用程式監聽器 -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>

這裡有兩個入口類:

這兩個類分別定義在 spring-webmvcspring-web 中,下面對他們一一進行解析。

3. DispatcherServlet

先來看看 DispatcherServlet 的繼承關係:

- javax.servlet.Servlet
  - javax.servlet.GenericServlet
    - javax.servlet.http.HttpServlet
      - HttpServletBean
        - FrameworkServlet
          - DispatcherServlet

3.1. javax.servlet.Servlet

首先看看 javax.servlet.Servlet

javax.servlet.Servlet 主要定義了 2 個方法:

  • init:初始化 Servlet,只執行一次
  • service:響應請求,每次 http 請求都會呼叫這個方法
public interface Servlet {
    // 初始化 Servlet,只執行一次
    public void init(ServletConfig config) throws ServletException;

    // 響應請求,每次http請求都會呼叫這個方法
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

    // 銷燬 Servlet
    public void destroy();
}

3.2. javax.servlet.GenericServlet

再來看看 javax.servlet.GenericServlet

javax.servlet.GenericServlet 主要是過載了 init 方法

public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable {
    public GenericServlet() {}

    // 新增配置初始化
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    // 保留無參初始化
    public void init() throws ServletException {}

    // 留給子類實現
    public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
}

3.3. javax.servlet.http.HttpServlet

再來看看 javax.servlet.http.HttpServlet

javax.servlet.http.HttpServlet 主要是過載了 service 方法,並擴充套件了 7 個方法:

  • doGet:處理 GET 請求,只輸入錯誤資訊,未實現
  • doHead:處理 HEAD 請求,只輸入錯誤資訊,未實現
  • doPost:處理 POST 請求,只輸入錯誤資訊,未實現
  • doPut:處理 PUT 請求,只輸入錯誤資訊,未實現
  • doDelete:處理 DELETE 請求,只輸入錯誤資訊,未實現
  • doOptions:處理 OPTIONS 請求
  • doTrace:處理 TRACE 請求
public abstract class HttpServlet extends GenericServlet {
    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_HEAD = "HEAD";
    private static final String METHOD_GET = "GET";
    private static final String METHOD_OPTIONS = "OPTIONS";
    private static final String METHOD_POST = "POST";
    private static final String METHOD_PUT = "PUT";
    private static final String METHOD_TRACE = "TRACE";

    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_get_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }

    protected void doHead(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        NoBodyResponse response = new NoBodyResponse(resp);
        // 呼叫 doGet,但body設為空body
        doGet(req, response);
        response.setContentLength();
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        // ... 程式碼省略
    }

    protected void doPut(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        // ... 程式碼省略
    }

    protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        // ... 程式碼省略
    }

    protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        // ... 程式碼省略
    }

    protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        // ... 程式碼省略
    }

    // 實現了GET, HEAD, POST PUT, DELETE, OPTIONS, TRACE七個Http方法
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            // ... 程式碼省略
            doGet(req, resp);
            // ... 程式碼省略
        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);
        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

    // 把 Servlet 轉化為 HttpServlet
    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException
    {
        HttpServletRequest  request;
        HttpServletResponse response;

        if (!(req instanceof HttpServletRequest &&
                res instanceof HttpServletResponse)) {
            throw new ServletException("non-HTTP request or response");
        }

        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;

        service(request, response);
    }
}

3.4. HttpServletBean

再來看看 HttpServletBean

HttpServletBean 主要是過載了 init 方法,並擴充套件了 2 個方法:

  • initBeanWrapper:初始化由 Servlet Config 定義的 Java Bean,由子類實現,預設不實現
  • initServletBean:初始化 Servlet bean,由子類實現
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
    // 初始化
    @Override
    public final void init() throws ServletException {
        // Set bean properties from init parameters.
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }

        // Let subclasses do whatever initialization they like.
        initServletBean();
    }

    // 初始化由Servlet Config定義的Java Bean,由子類實現,預設不實現
    protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
    }

    // 初始化Servlet bean,由子類實現
    protected void initServletBean() throws ServletException {
    }
}

3.5. FrameworkServlet

再來看看 FrameworkServlet

FrameworkServlet 是框架的核心 Servlet,主要是過載了 initServletBean 方法,並擴充套件了 2 個方法:

  • initFrameworkServlet:初始化框架 Servlet,由子類實現,預設不實現
  • onRefresh:重新整理上下文資料,由子類實現

過載了 service, doGet, doPost, doPut, doDelete, doOptions, doTrace 方法,並擴充套件了 1 個方法:

  • doService:處理響應請求

3.5.1. FrameworkServlet.initServletBean

父類 HttpServletBean 初始化後,留下兩個鉤子 initBeanWrapper, initServletBeaninitBeanWrapper 預設並不實現,所以來看看 initServletBean 鉤子的實現:FrameworkServlet.initServletBean

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    @Override
    protected final void initServletBean() throws ServletException {
        // ... 程式碼省略

        try {
            // 初始化Web應用上下文
            this.webApplicationContext = initWebApplicationContext();
            // 初始化Web框架Servlet
            initFrameworkServlet();
        }
        catch (ServletException | RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            throw ex;
        }

        // ... 程式碼省略
    }

    // 初始化框架Servlet,由子類實現,預設不實現
    protected void initFrameworkServlet() throws ServletException {}
}

再來看看 FrameworkServlet.initWebApplicationContext

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    protected WebApplicationContext initWebApplicationContext() {
        // 獲取應用根上下文
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        if (this.webApplicationContext != null) {
            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                // 未啟用
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        cwac.setParent(rootContext);
                    }
                    // 配置並重新整理應用上下文
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            // 如果沒有,則在ServletContext中查詢是否註冊過
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            // 如果任然沒有,則以rootContext為父上下文建立一個新的上下文
            // 並呼叫 configureAndRefreshWebApplicationContext(cwac) 配置並重新整理新的上下文
            // 預設使用 XmlWebApplicationContext(基於XML載入)作為應用上下文
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            // 過載上下文資料
            synchronized (this.onRefreshMonitor) {
                onRefresh(wac);
            }
        }

        if (this.publishContext) {
            // 把上下文註冊到ServletContext中
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
        }

        return wac;
    }


    // 以parent為父上下文建立一個新的上下文
    // 並呼叫 configureAndRefreshWebApplicationContext(cwac) 配置並重新整理新的上下文
    // 預設使用 XmlWebApplicationContext(基於XML載入)作為應用上下文
    protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
        // 這裡預設使用 XmlWebApplicationContext(基於XML載入)
        Class<?> contextClass = getContextClass();
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

        wac.setEnvironment(getEnvironment());
        wac.setParent(parent);
        String configLocation = getContextConfigLocation();
        if (configLocation != null) {
            wac.setConfigLocation(configLocation);
        }
        configureAndRefreshWebApplicationContext(wac);

        return wac;
    }
}

這其中有兩個方法需要深入解析:configureAndRefreshWebApplicationContext, onRefresh

再來看看 FrameworkServlet.configureAndRefreshWebApplicationContext

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
        // ... 程式碼省略

        // 設定ServletContext
        wac.setServletContext(getServletContext());
        // 設定ServletConfig
        wac.setServletConfig(getServletConfig());
        wac.setNamespace(getNamespace());
        // 新增應用事件監聽器,應用事件會觸發當前物件的onApplicationEvent方法
        // 進一步,會呼叫當前物件的onRefresh方法,重新整理上下文資料
        wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

        // ... 程式碼省略

        // 初始化一些需要初始載入的類,呼叫這些類的initialize方法
        applyInitializers(wac);
        // 應用上下文重新整理
        wac.refresh();
    }

    // 應用事件會觸發此方法,然後呼叫當前物件的onRefresh方法,重新整理上下文資料
    public void onApplicationEvent(ContextRefreshedEvent event) {
        this.refreshEventReceived = true;
        synchronized (this.onRefreshMonitor) {
            onRefresh(event.getApplicationContext());
        }
    }
}

再來看看 FrameworkServlet.onRefresh

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    protected void onRefresh(ApplicationContext context) {
        // 由子類來實現
    }
}

3.5.2. FrameworkServlet.service

再來看看 FrameworkServlet.service

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        // 如果Http方法是Patch或沒有,擴充套件處理
        if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
            processRequest(request, response);
        }
        else {
            super.service(request, response);
        }
    }

    @Override
    protected final void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 擴充套件處理
        processRequest(request, response);
    }

    @Override
    protected final void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 擴充套件處理
        processRequest(request, response);
    }

    @Override
    protected final void doPut(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 擴充套件處理
        processRequest(request, response);
    }

    @Override
    protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 擴充套件處理
        processRequest(request, response);
    }

    @Override
    protected void doOptions(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {
            // 擴充套件處理
            processRequest(request, response);
            if (response.containsHeader("Allow")) {
                // Proper OPTIONS response coming from a handler - we're done.
                return;
            }
        }

        // ... 程式碼省略
    }

    @Override
    protected void doTrace(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        if (this.dispatchTraceRequest) {
            // 擴充套件處理
            processRequest(request, response);
            if ("message/http".equals(response.getContentType())) {
                // Proper TRACE response coming from a handler - we're done.
                return;
            }
        }
        super.doTrace(request, response);
    }
}

再來看看擴充套件處理方法 FrameworkServlet.processRequest

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // ... 程式碼省略

        // 記錄請求屬性與上下文環境,請求處理完後派發事件
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = buildLocaleContext(request);

        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

        initContextHolders(request, localeContext, requestAttributes);

        try {
            doService(request, response);
        }
        // ... 程式碼省略

        finally {
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }
            logResult(request, response, failureCause, asyncManager);
            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }

    // 由子類來實現
    protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
            throws Exception;
}

3.6. DispatcherServlet

DispatcherServlet 主要擴充套件了 2 個方法:onRefreshdoService,所以來看看 DispatcherServlet 是如何實現的

3.6.1. DispatcherServlet.onRefresh

DispatcherServlet.onRefresh

public class DispatcherServlet extends FrameworkServlet {
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    protected void initStrategies(ApplicationContext context) {
        // 初始化Multipart檔案上傳處理
        initMultipartResolver(context);
        // 初始化本地化處理
        initLocaleResolver(context);
        // 初始化主題處理
        initThemeResolver(context);
        // 初始化處理器對映
        initHandlerMappings(context);
        // 初始化處理器適配
        initHandlerAdapters(context);
        // 初始化處理器異常
        initHandlerExceptionResolvers(context);
        // 初始化檢視查詢處理
        initRequestToViewNameTranslator(context);
        // 初始化檢視解析處理
        initViewResolvers(context);
        // 初始化記憶體暫存session資料管理器
        initFlashMapManager(context);
    }

    private void initMultipartResolver(ApplicationContext context) {
        try {
            // 獲取bean
            this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);

            // ... 程式碼省略
        }
        catch (NoSuchBeanDefinitionException ex) {
            // ... 程式碼省略
        }
    }

    private void initLocaleResolver(ApplicationContext context) {
        try {
            // 獲取bean
            this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);

            // ... 程式碼省略
        }
        catch (NoSuchBeanDefinitionException ex) {
            // ... 程式碼省略
        }
    }

    private void initThemeResolver(ApplicationContext context) {
        try {
            // 獲取bean
            this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);

            // ... 程式碼省略
        }
        catch (NoSuchBeanDefinitionException ex) {
            // ... 程式碼省略
        }
    }

    private void initFlashMapManager(ApplicationContext context) {
        try {
            // 獲取bean
            this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // 沒有bean,則獲取預設策略
            this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
        }
    }
}
3.6.1.1. DispatcherServlet.initHandlerMappings

DispatcherServlet.initHandlerMappings

public class DispatcherServlet extends FrameworkServlet {
    private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        // 預設是探測所有的HandlerMapping,包括父上下文
        if (this.detectAllHandlerMappings) {
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<>(matchingBeans.values());
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            // 否則直接獲取bean
            try {
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {}
        }

        // 如果以上兩種都沒有定義,則獲取預設的處理策略
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        }

        // ... 程式碼省略
    }

    // 獲取預設的處理策略
    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        // 嘗試從DispatcherServlet.properties檔案中載入
        if (defaultStrategies == null) {
            try {
                ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
                defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
            }
            catch (IOException ex) {
                throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
            }
        }

        String key = strategyInterface.getName();
        String value = defaultStrategies.getProperty(key);
        if (value != null) {
            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
            List<T> strategies = new ArrayList<>(classNames.length);
            for (String className : classNames) {
                try {
                    Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                    // 建立bean
                    Object strategy = createDefaultStrategy(context, clazz);
                    // 裝載到 strategies 中
                    strategies.add((T) strategy);
                }
                catch (ClassNotFoundException ex) {
                    // ... 程式碼省略
                }
                catch (LinkageError err) {
                    // ... 程式碼省略
                }
            }
            return strategies;
        }
        else {
            return Collections.emptyList();
        }
    }

    // 建立bean
    protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
        return context.getAutowireCapableBeanFactory().createBean(clazz);
    }
}

DispatcherServlet.properties 檔案(開發者不能自定義覆蓋)如下:

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
    org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
    org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

DispatcherServlet.properties 檔案中指明:

  • AcceptHeaderLocaleResolver 作為預設的本地化解析器
  • FixedThemeResolver 作為預設的主題解析器
  • BeanNameUrlHandlerMapping, RequestMappingHandlerMapping, RouterFunctionMapping 作為預設的處理器對映元件
  • HttpRequestHandlerAdapter, SimpleControllerHandlerAdapter, RequestMappingHandlerAdapter, HandlerFunctionAdapter 作為預設的處理器適配元件
  • ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver, DefaultHandlerExceptionResolver 作為預設的處理器異常解析器
  • DefaultRequestToViewNameTranslator 作為預設的檢視查詢處理器
  • InternalResourceViewResolver 作為預設的檢視解析器
  • SessionFlashMapManager 作為預設的記憶體暫存 session 資料管理器
3.6.1.2. DispatcherServlet.initHandlerAdapters

DispatcherServlet.initHandlerAdapters

public class DispatcherServlet extends FrameworkServlet {
    private void initHandlerAdapters(ApplicationContext context) {
        this.handlerAdapters = null;

        // 預設是探測所有的HandlerAdapter,包括父上下文
        if (this.detectAllHandlerAdapters) {
            Map<String, HandlerAdapter> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerAdapters = new ArrayList<>(matchingBeans.values());
                AnnotationAwareOrderComparator.sort(this.handlerAdapters);
            }
        }
        else {
            // 否則直接獲取bean
            try {
                HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
                this.handlerAdapters = Collections.singletonList(ha);
            }
            catch (NoSuchBeanDefinitionException ex) {}
        }

        // 如果以上兩種都沒有定義,則獲取預設的處理策略
        if (this.handlerAdapters == null) {
            this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
        }
    }
}
3.6.1.3. DispatcherServlet.initHandlerExceptionResolvers

DispatcherServlet.initHandlerExceptionResolvers

public class DispatcherServlet extends FrameworkServlet {
    private void initHandlerExceptionResolvers(ApplicationContext context) {
        this.handlerExceptionResolvers = null;

        // 預設是探測所有的HandlerExceptionResolver,包括父上下文
        if (this.detectAllHandlerExceptionResolvers) {
            Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
                    .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
                AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
            }
        }
        else {
            // 否則直接獲取bean
            try {
                HandlerExceptionResolver her =
                        context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
                this.handlerExceptionResolvers = Collections.singletonList(her);
            }
            catch (NoSuchBeanDefinitionException ex) {}
        }

        // 如果以上兩種都沒有定義,則獲取預設的處理策略
        if (this.handlerExceptionResolvers == null) {
            this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
        }
    }
}
3.6.1.4. DispatcherServlet.initRequestToViewNameTranslator

DispatcherServlet.initRequestToViewNameTranslator

public class DispatcherServlet extends FrameworkServlet {
    private void initRequestToViewNameTranslator(ApplicationContext context) {
        try {
            // 獲取bean
            this.viewNameTranslator =
                    context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // 如果沒有定義bean,則獲取預設的處理策略
            this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
        }
    }
}
3.6.1.5. DispatcherServlet.initViewResolvers

DispatcherServlet.initViewResolvers

public class DispatcherServlet extends FrameworkServlet {
    private void initViewResolvers(ApplicationContext context) {
        this.viewResolvers = null;

        // 預設是探測所有的ViewResolver,包括父上下文
        if (this.detectAllViewResolvers) {
            Map<String, ViewResolver> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.viewResolvers = new ArrayList<>(matchingBeans.values());
                AnnotationAwareOrderComparator.sort(this.viewResolvers);
            }
        }
        else {
            // 否則直接獲取bean
            try {
                ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
                this.viewResolvers = Collections.singletonList(vr);
            }
            catch (NoSuchBeanDefinitionException ex) {}
        }

        // 如果以上兩種都沒有定義,則獲取預設的處理策略
        if (this.viewResolvers == null) {
            this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
        }
    }
}

3.6.2. DispatcherServlet.doService

剛剛解析完了 DispatcherServlet.onRefresh,現在來看看 DispatcherServlet.doService

public class DispatcherServlet extends FrameworkServlet {
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // ... 程式碼省略

        // 給請求物件新增一些上下文資料
        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());

        // ... 程式碼省略

        try {
            doDispatch(request, response);
        }
        finally {
            // ... 程式碼省略
        }
    }

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        // 處理器鏈
        HandlerExecutionChain mappedHandler = null;
        // 是Multipart檔案上傳
        boolean multipartRequestParsed = false;
        // 非同步處理管理器
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                // 檢測Multipart檔案上傳
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // 獲取處理器,從handlerMappings中查詢符合請求的處理器
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    // 未找到處理器,404
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // 獲取處理器介面卡,從handlerAdapters中查詢符合處理器的介面卡
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                String method = request.getMethod();

                // 如果是GET或HEAD請求,檢查Last-Modified
                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;
                    }
                }

                // 前置處理,呼叫處理器的preHandle方法,如果有一個不成功,返回
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 呼叫處理器
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                // ... 程式碼省略

                // 如果沒有檢視名字,新增預設的檢視名
                applyDefaultViewName(processedRequest, mv);
                // 後置處理,呼叫處理器的postHandle方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                // ... 程式碼省略
            }
            catch (Throwable err) {
                // ... 程式碼省略
            }

            // 處理handler返回的結果
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            // ... 程式碼省略
        }
        catch (Throwable err) {
            // ... 程式碼省略
        }
        finally {
            // ... 程式碼省略
        }
    }

    // 處理handler返回的結果
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
            @Nullable Exception exception) throws Exception {

        boolean errorView = false;

        if (exception != null) {
            // ... 程式碼省略,如果有異常,呼叫handlerExceptionResolvers處理
        }

        if (mv != null && !mv.wasCleared()) {
            // 渲染檢視
            render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        }
        // ... 程式碼省略
    }

    // 渲染檢視
    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // ... 程式碼省略

        View view;
        String viewName = mv.getViewName();
        if (viewName != null) {
            // 呼叫viewResolvers來解析檢視
            view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
            // ... 程式碼省略
        }
        else {
            // ... 程式碼省略
        }

        // ... 程式碼省略
        try {
            if (mv.getStatus() != null) {
                // 設定http狀態碼
                response.setStatus(mv.getStatus().value());
            }
            // 真實渲染
            view.render(mv.getModelInternal(), request, response);
        }
        catch (Exception ex) {
            // ... 程式碼省略
        }
    }
}

3.6.3. 需要後面再解析的幾個點

DispatcherServlet 這個類的解析基本上就差不多了,但還有幾點沒有解析:

這幾點,我們後面再來解析。

4. ContextLoaderListener

先來看看 ContextLoaderListener 的繼承關係:

- ContextLoader
  - ContextLoaderListener

ContextLoaderListener 比較簡單,只有兩個監聽事件方法

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent event) {
        // ContextLoader.initWebApplicationContext
        initWebApplicationContext(event.getServletContext());
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        // ContextLoader.closeWebApplicationContext
        closeWebApplicationContext(event.getServletContext());
        // 銷燬上下文中以"org.springframework."開頭的可銷燬bean
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

ContextLoader 的靜態初始化

public class ContextLoader {
    static {
        try {
            // 從ContextLoader.properties檔案中載入預設的策略
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ex) {
            throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
        }
    }
}

ContextLoader.properties 檔案的內容如下:

# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

ContextLoader.properties 檔案中指明使用 XmlWebApplicationContext 作為預設的 Web 應用上下文環境

再來看看 ContextLoaderinitWebApplicationContextcloseWebApplicationContext

4.1. ContextLoaderListener.initWebApplicationContext

ContextLoaderListener.initWebApplicationContext

public class ContextLoader {
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        // ... 程式碼省略

        try {
            // 如果沒有上下文物件,則建立一個新的上下文
            // 並呼叫 configureAndRefreshWebApplicationContext(cwac) 配置並重新整理新的上下文
            // 預設使用 XmlWebApplicationContext(基於XML載入)作為應用上下文
            if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                // 未啟用
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    // 配置並重新整理應用上下文
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }

            // 把上下文註冊到ServletContext中
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

            // ... 程式碼省略

            return this.context;
        }
        catch (RuntimeException | Error ex) {
            // ... 程式碼省略
        }
    }
}

ContextLoader.configureAndRefreshWebApplicationContextFrameworkServlet.configureAndRefreshWebApplicationContext 的處理基本上一致。

也就是說,當容器啟動(如 Tomcat、Jetty、Undertow 等)時,Spring 框架會自動進行初始化。

4.2. ContextLoaderListener.closeWebApplicationContext

ContextLoaderListener.closeWebApplicationContext

public class ContextLoader {
    public void closeWebApplicationContext(ServletContext servletContext) {
        try {
            if (this.context instanceof ConfigurableWebApplicationContext) {
                // 呼叫上下文物件的close方法
                ((ConfigurableWebApplicationContext) this.context).close();
            }
        }
        finally {
            // ... 程式碼省略
        }
    }
}

5. 綜述

DispatcherServlet.initContextLoaderListener.contextInitialized 都會進行應用上下文的初始化,主要過程是:

  1. 初始化 Web 應用上下文,預設使用 XmlWebApplicationContext(基於 XML 載入)作為應用上下文,並呼叫 refresh 方法
  2. 例項化由 globalInitializerClassescontextInitializerClasses 定義的類
  3. 例項化 WebMVC 必要的元件:MultipartResolver, LocaleResolver, ThemeResolver, HandlerMapping, HandlerAdapter, HandlerExceptionResolver, RequestToViewNameTranslator, ViewResolver, FlashMapManager

每個請求都會進入到 DispatcherServlet.service,其主要過程是:

  1. 初始化請求物件,以便應用後續處理
  2. 處理 Multipart 檔案上傳,獲取處理器處理當前請求
  3. 如果當前請求處理髮生異常,進行異常處理
  4. 進行檢視渲染

6. 未完

到這裡為止,分析僅僅止於 DispatcherServletContextLoaderListener 兩個類,下一篇將深入其他類,繼續探索。

  • ConfigurableWebApplicationContext.refresh 重新整理上下文
  • ApplicationContext.getBean 從上下文中獲取 bean
  • DispatcherServlet.properties 檔案中定義的策略處理
  • ContextLoader.properties 檔案中定義的策略處理
  • View.render 檢視渲染

後續

更多部落格,檢視 https://github.com/senntyou/blogs

作者:深予之 (@senntyou)

版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證

相關文章