SpringBoot中Tomcat和SpringMVC整合原始碼分析

2022-07-25 06:01:25

概述

​ SpringBoot中整合官方的第三方元件是通過在POM檔案中新增元件的starter的Maven依賴來完成的。新增相關的Maven依賴之後,會引入具體的jar包,在SpringBoot啟動的時候會根據預設自動裝配的設定類的注入條件判斷是否注入該自動設定類到Spring容器中。自動設定類中會建立具體的第三方元件需要的類 。Tomcat和SpringMVC都是通過這樣的方式進行整合的。SpringBoot出現之前SpringMVC專案是直接部署在Tomcat伺服器中的,Tomcat是一個符合Servlet標準的Web伺服器,Tomcat單獨作為一個可安裝軟體。這種方式下Tomcat是一個完整獨立的web伺服器,SpringMVC專案不能脫離Web伺服器直接執行,需要先部署在Tomcat伺服器的目錄中才能執行。SpringBoot出現之後改變了這種方式,我們可以直接執行SpringBoot專案,因為SpringBoot中可以內嵌tomcat伺服器,而tomcat也是java開發的。在啟動SpringBoot專案的時候直接建立tomcat服務並且把SpringMVC的專案部署在tomcat服務中。

一、自動裝配原理

​ 在SpringBoot自動裝配的程中,會載入/META-INF/factories檔案中的以EnableAutoConfiguration為Key的設定類的字串陣列,在該類中會根據具體引入的伺服器類進行建立具體的伺服器物件。這些字串陣列是否真正載入進Spring容器,需要經過兩方面的判斷,

​ 1.根據META-INF/spring-autoconfigure-metadata.properties檔案中設定的規則判斷是否需要過濾。

​ 2.在org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass中會根據自動裝配類的條件註解來判斷是否進行自動載入。如果條件註解全部校驗成功才會載入該設定類。

兩種情況的底層校驗邏輯都是一樣的,都是通過呼叫OnWebApplicationCondition、OnClassCondition、OnBeanCondition的match方法進行判斷。但是在processConfigurationClass方式的檢查型別會更多,比如ConditionalOnMissingBean 等條件的檢查。

條件註解 註解備註
OnWebApplicationCondition 判斷是否符合伺服器型別
OnClassCondition 判斷專案中是否存在設定的類
OnBeanCondition 判斷Spring容器中是否注入設定的類

二、內嵌式Tomcat注入

2.1自動注入設定類分析

1.ServletWebServerFactoryAutoConfiguration 設定類分析

​ 1.META-INF/factories檔案中以EnableAutoConfiguration為Key的設定類的字串陣列,其中 ServletWebServerFactoryAutoConfiguration 為建立Web伺服器的自動設定類,根據META-INF/spring-autoconfigure-metadata.properties檔案中設定的規則判斷是否需要過濾,該檔案關於ServletWebServerFactoryAutoConfiguration的設定描述如下,

org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration.ConditionalOnClass=javax.servlet.ServletRequest
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration.ConditionalOnWebApplication=SERVLET
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration.AutoConfigureOrder=-2147483648

此設定說明需要在當前的專案中有javax.servlet.ServletRequest類並且WebApplication是SERVLET容器,才不會過濾。

  1. 根據自動裝配類的條件註解來判斷是否進行自動載入,ServletWebServerFactoryAutoConfiguration 設定類的條件註解如下,
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// 判斷是否有 ServletRequest 類
@ConditionalOnClass(ServletRequest.class)
// 判斷 WebApplication 是 SERVLET 容器
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)

此設定說明需要在當前的專案中有javax.servlet.ServletRequest類並且WebApplication是SERVLET容器,才會進行ServletWebServerFactoryAutoConfiguration 類的載入。

2.2注入邏輯具體分析

  1. tomcat的starter依賴設定程式碼如下,
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.2.5.RELEASE</version>
    <scope>compile</scope>
</dependency>
  1. 新增Maven依賴設定後,會引入tomcat-embed-corejar包,其中有ServletRequest介面。程式碼截圖如下,

SpringBoot的在建立伺服器型別的時候是Servlet的,在建立SpringApplication的run方法中會呼叫createApplicationContext方法建立具體的AnnotationConfigServletWebServerApplicationContext類,該類繼承了org.springframework.web.context.support.GenericWebApplicationContext類。OnWebApplicationCondition條件註解中就是通過判斷是否有GenericWebApplicationContext類來檢查是否是SERVLET型別的。

  1. 所以新增完tomcat的starter依賴設定後,ServletWebServerFactoryAutoConfiguration所有的條件註解都會匹配成功 ,即會自動載入 ServletWebServerFactoryAutoConfiguration 自動裝配類。該類會通過@Import註解匯入3個內嵌伺服器的實現類,這3個實現類的伺服器都是Servlet標準的。程式碼如下,
// 匯入具體伺服器類
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
         ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
         ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
         ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })

三個Embeddedxxx表示相應的伺服器實現類,每個伺服器類都有自己的條件註解實現不同的載入邏輯,如果這裡注入了多個web伺服器,在伺服器啟動的時候會報錯。EmbeddedTomcat類的程式碼如下,

@Configuration(proxyBeanMethods = false)
#當前工程下需要有Servlet、Tomcat UpgradeProtocol類
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
#當前的Spring容器類不能有ServletWebServerFactory型別的Bean,搜尋的是當前容器。
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
    @Bean
    TomcatServletWebServerFactory tomcatServletWebServerFactory(
        ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
        ObjectProvider<TomcatContextCustomizer> contextCustomizers,
        ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
        // 建立 TomcatServletWebServerFactory ,是產生TomcatServletWebServer的工廠類
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.getTomcatConnectorCustomizers()
            .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
        factory.getTomcatContextCustomizers()
            .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
        factory.getTomcatProtocolHandlerCustomizers()
            .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
        return factory;
    }
}

從以上程式碼看出,EmbeddedTomcat設定類能載入注入的條件是當前工程下需要有Servlet、Tomcat、 UpgradeProtocol3個類並且當前Spring容器沒有注入ServletWebServerFactory型別的Bean。而這個三個類都在tomcat-embed-core.jar包中,並且Spring容器中之前沒有注入ServletWebServerFactory型別的Bean,所會自動載入EmbeddedTomcat類。EmbeddedTomcat用來注入TomcatServletWebServerFactory到Spring容器,TomcatServletWebServerFactory的實現了ServletWebServerFactory介面,TomcatServletWebServerFactory可以建立具體的TomcatWebServer類。在SpringBoot啟動的時候會從Spring容器中獲取ServletWebServerFactory型別的Bean。至此Tomcat的自動裝置解析完了。

三、SpringMVC注入

​ SpringMVC中最重要的元件是DispatchServlet,SpringMVC要在Servlet型別的Web伺服器執行,就要把Servlet新增到Web容器中,用Servlet來處理請求。回想一下以前的SpringMVC,我們需要在web.xml組態檔中新增Servlet的設定,會預設把SpringMVC的DispatchServlet設定到Servlet設定節點中,web.xm設定節點程式碼如下,

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
            </init-param>
            <!--Web伺服器一旦啟動,Servlet就會範例化建立物件,然後初始化(預備建立物件)-->
            <load-on-startup>1</load-on-startup>
</servlet>

在SpringBoot中我們使用的是內嵌式的Tomcat,那tomcat和SpringMVC的DispatchServlet是怎麼關聯起來的?一起往下看,META-INF/factories檔案中的以EnableAutoConfiguration為Key的設定類的字串陣列,其中 DispatcherServletAutoConfiguration 和 WebMvcAutoConfiguration 是SpringMVC中最為重要的兩個自動設定類,DispatcherServletAutoConfiguration 用來註冊 DispatcherServlet到 Spring容器,WebMvcAutoConfiguration 用來注入MVC的相關元件到Spring容器。

3.1自動注入設定類分析

1.DispatcherServletAutoConfiguration 設定類分析

​ 1.1 根據META-INF/spring-autoconfigure-metadata.properties檔案中設定的規則判斷是否需要過濾,該檔案關於DispatcherServletAutoConfiguration 的設定描述如下,

#WebApplication是SERVLET容器
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.ConditionalOnWebApplication=SERVLET
#工程中需要引入 DispatcherServlet 類
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.ConditionalOnClass=org.springframework.web.servlet.DispatcherServlet
#容器注入在 ServletWebServerFactoryAutoConfiguration(伺服器自動設定類) 類之後,
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.AutoConfigureOrder=-2147483648

DispatcherServletAutoConfiguration 的邏輯為:需要在當前的專案中有DispatcherServlet類並且WebApplication是SERVLET容器,才不會過濾掉。

​ 1.2 根據自動裝配類的條件註解來判斷是否進行自動載入,DispatcherServletAutoConfiguration 設定類的條件註解如下,

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)

此邏輯需要在當前的專案中有DispatcherServlet類並且WebApplication是SERVLET容器,並且注入在 DispatcherServletAutoConfiguration 類之後,才會進行DispatcherServletAutoConfiguration 類的載入。在DispatcherServletAutoConfiguration 中有兩個非常重要的內部類,DispatcherServletConfiguration 和DispatcherServletRegistrationConfiguration ,這兩個內部類也會根據條件註解載入來決定是否載入進Spring容器中 。DispatcherServletConfiguration 主要用來注入 DispatcherServlet ;DispatcherServletRegistrationConfiguration 主要用來注入 DispatcherServletRegistrationBean,DispatcherServletRegistrationBean 用來包裝 DispatcherServlet 類,它實現了ServletContextInitializer介面,用來註冊Servlet物件到tomcat中的ServletContext物件。

2.DispatcherServletConfiguration 設定類分析

​ DispatcherServletConfiguration 中有個DispatcherServlet型別的@Bean物件,會把DispatcherServlet注入到Spring容器中,DispatcherServletConfiguration 程式碼如下,

@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
protected static class DispatcherServletConfiguration {
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
        return dispatcherServlet;
    }
}

以上條件註解程式碼可知,將BeanName為 dispatcherServlet ,型別為DispatcherServlet 的Bean注入到容器中的條件為:先查詢是否已經容器中是否存在名稱 dispatcherServlet的Bean或者型別DispatcherServlet 的Bean,如果都不存並且當前工程中有ServletRegistration類則將DispatcherServletConfiguration注入到Spring容器中,並且還會注入DispatcherServlet類。

  1. DispatcherServletRegistrationConfiguration 設定類分析

​ DispatcherServletRegistrationConfiguration 主要用來注入 DispatcherServletRegistrationBean,DispatcherServletRegistrationBean 用來包裝 DispatcherServlet 類,它實現了ServletContextInitializer介面,SpringBoot用DispatcherServletRegistrationBean 來註冊Servlet物件到Tomcat伺服器中的ServletContext物件,在後面Tomcat啟動的時候會講具體實現。DispatcherServletRegistrationConfiguration程式碼如下,

@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
                                                                           WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
        DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                                                                                               webMvcProperties.getServlet().getPath());
        registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
        registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        multipartConfig.ifAvailable(registration::setMultipartConfig);
        return registration;
    }
}

以上條件註解程式碼可知,將BeanName為 dispatcherServletRegistration,型別為DispatcherServletRegistrationBean的Bean注入到容器中的條件為:先查詢是否已經容器中是否存在名稱 dispatcherServletRegistration的Bean或者型別DispatcherServletRegistrationBean的Bean,如果都不存並且當前工程中有ServletRegistration類則將DispatcherServletRegistrationConfiguration注入到Spring容器中,並且還會注入DispatcherServletRegistrationBean類。

  1. WebMvcAutoConfiguration

​ 4. 1 根據META-INF/spring-autoconfigure-metadata.properties檔案中設定的規則判斷是否需要過濾,該檔案關於 WebMvcAutoConfiguration 的設定描述如下,

#工程中需要引入Servlet、WebMvcConfigurer、 DispatcherServlet類 org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.ConditionalOnClass=javax.servlet.Servlet,org.springframework.web.servlet.config.annotation.WebMvcConfigurer,org.springframework.web.servlet.DispatcherServlet
# 容器注入在 DispatcherServletAutoConfiguration、TaskExecutionAutoConfiguration、ValidationAutoConfiguration之
# 後
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigureOrder=-2147483638

WebMvcAutoConfiguration的邏輯為:需要在當前的專案中有Servlet、WebMvcConfigurer、DispatcherServlet3個類,才不會過濾掉。

​ 4.2 根據自動裝配類的條件註解來判斷是否進行自動載入,WebMvcAutoConfiguration設定類的條件註解如下,

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })

此邏輯需要當前的應用程式為SERVLET型別,當前的專案中有Servlet、DispatcherServlet、WebMvcConfigurer3個類、WebMvcConfigurationSupport沒有被注入到Spring容器、在 DispatcherServletAutoConfiguration 、TaskExecutionAutoConfiguration、ValidationAutoConfiguration 類注入到Spring容器之後,才會進行WebMvcAutoConfiguration類的注入。在該中有個內部類WebMvcAutoConfigurationAdapter ,主要用來注入 SpringMVC的元件,比如ViewResolver、LocaleResolver等。

3.2 注入邏輯具體分析

  1. springmvc的starter依賴設定程式碼如下
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.4.RELEASE</version>
    <scope>compile</scope>
</dependency>
  1. 新增Maven依賴設定後,會引入spring-webmvc.jar包,其中有DispatcherServlet類、WebMvcConfigurer介面。而springmvc的自動設定類在ServletWebServerFactoryAutoConfiguration設定類載入之後進行載入,所以肯定會有Servlet的存在。

  2. 所以新增完springmvc的Maven依賴設定後,會自動載入 DispatcherServletAutoConfiguration類和WebMvcAutoConfiguration類以及他們設定類中其他的設定類。至此SpringMVC的自動裝置解析完了。

四、Tomcat與SpringMVC整合

  1. 在SpringApplication的run方法中,會進行ApplicationContext的建立,然後會執行 refreshContext 方法,其中的會執行onRefresh方法,這個方法是由子類ServletWebServerApplicationContext實現,ServletWebServerApplicationContext中onRefresh方法程式碼如下,
protected void onRefresh() {
    super.onRefresh();
    try {
        // 建立 web 伺服器
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}
  1. createWebServer裡面的邏輯主要是建立web伺服器,主要程式碼如下,
private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    // 如果為 null,則建立
    if (webServer == null && servletContext == null) {
        // 獲取WebServerFactory
        ServletWebServerFactory factory = getWebServerFactory();
       // 獲取 webServer ,getSelfInitializer() 返回的是 函數式介面
	   // 是一個 實現了@FunctionalInterface 介面的列表
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    initPropertySources();
}
  1. 建立伺服器分為兩步,第一步獲取WebServerFactory 伺服器工廠,第二步根據工廠獲取具體的web伺服器。getWebServerFactory()主要邏輯為:先獲 BeanFactory ,然後從 BeanFactory取 ServletWebServerFactory 型別的 BeanNames陣列,如果陣列長度為 0,則拋異常,如果陣列長度大於 1,則拋異常,最後根據第一個beanName和ServletWebServerFactory型別獲取容器中的Bean進行返回。
  2. getWebServerFactory方法原始碼如下,
protected ServletWebServerFactory getWebServerFactory() {
    // 先獲 BeanFactory ,再從 BeanFactory取 ServletWebServerFactory 型別的 BeanNames
    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
    // 如果長度為 0,則拋異常
    if (beanNames.length == 0) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
                                              + "ServletWebServerFactory bean.");
    }
    // 如果長度大於 1,則拋異常
    if (beanNames.length > 1) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
                                              + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
    }
    // 返回根據第一個beanName和ServletWebServerFactory型別獲取容器中的Bean
    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

因為之前是注入了Tomcat的TomcatServletWebServerFactory,這裡返回的是前面注入到容器裡面的 TomcatServletWebServerFactory,然後呼叫該類的getWebServer方法,傳入getSelfInitializer()方法引數,這個時候不會進行getSelfInitializer()裡面具體業務邏輯呼叫,而是生成實現了ServletContextInitializer介面的匿名類後傳入方法中。ServletContextInitializer介面程式碼如下,

@FunctionalInterface
public interface ServletContextInitializer {
	/**
	 * Configure the given {@link ServletContext} with any servlets, filters, listeners
	 * context-params and attributes necessary for initialization.
	 * @param servletContext the {@code ServletContext} to initialize
	 * @throws ServletException if any call against the given {@code ServletContext}
	 * throws a {@code ServletException}
	 */
	void onStartup(ServletContext servletContext) throws ServletException;
}
  1. getSelfInitializer()方法返回的是函數表示式,不會立馬執行方法內容。當tomcat伺服器啟動的時候會呼叫該匿名類的onStartup方法。getSelfInitializer方法中的主要邏輯為:找出實現了ServletContextInitializer 介面的類並注入容器 ,然後迴圈呼叫實現了ServletContextInitializer 介面的匿名類的onStartup()方法。在3.1 章節中注入了實現了ServletContextInitializer 介面的DispatcherServletRegistrationBean類,這裡會呼叫DispatcherServletRegistrationBean的onStartup()方法,而DispatcherServletRegistrationBean中包含了3.1中注入的DispatcherServlet 類。DispatcherServletRegistrationBean的onStartup方法的主要邏輯為:將當前類中的DispatcherServlet 新增到Tomcat 的 servletContext 中。getSelfInitializer關鍵程式碼如下,
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareWebApplicationContext(servletContext);
    registerApplicationScope(servletContext);
    // 注入 servletContext 型別的 bean
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    // 注入實現了ServletContextInitializer 介面的類 ,並進行迴圈呼叫onStartup
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}

上面的getServletContextInitializerBeans()方法主要邏輯為:建立一個ServletContextInitializerBeans類,它實現了AbstractCollection介面,可以作為集合直接遍歷。ServletContextInitializerBeans構造方法主要邏輯為:在容器中獲取所有實現了ServletContextInitializer 介面的類新增到this.initializers中,排序後賦值給sortedList屬性。getServletContextInitializerBeans()和ServletContextInitializerBeans的構造方法程式碼如下,

//getServletContextInitializerBeans具體程式碼
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    // 構造ServletContextInitializerBeans物件,該物件實現了 AbstractCollection<ServletContextInitializer> 介面
    // 是 ServletContextInitializer的 一個集合類。
    return new ServletContextInitializerBeans(getBeanFactory());
}

// ServletContextInitializerBeans 構造方法
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
                                      Class<? extends ServletContextInitializer>... initializerTypes) {
    // 初始化 initializers
    this.initializers = new LinkedMultiValueMap<>();
    this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
        : Collections.singletonList(ServletContextInitializer.class);
    // 在beanFactory中查詢實現了 ServletContextInitializer 介面的類
    // 這裡包括了  ServletRegistrationBean 、FilterRegistrationBean
    // DelegatingFilterProxyRegistrationBean、ServletListenerRegistrationBean 和其他。
    // 新增到 initializers 集合中
    addServletContextInitializerBeans(beanFactory);
    // 新增適配的 Bean
    addAdaptableBeans(beanFactory);
    // initializers 排序
    List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
        .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
        .collect(Collectors.toList());
    // 給該集合可遍歷物件賦值
    this.sortedList = Collections.unmodifiableList(sortedInitializers);
    // 返回 initializers
    logMappings(this.initializers);
}

addServletContextInitializerBeans()方法中會找到所有實現了 ServletContextInitializer 介面的類,即會找到3.1章節中解析的DispatcherServletRegistrationBean類,都新增到 this.initializers 集合中。

​ 5.1DispatcherServletRegistrationBean的onStartup方法主要邏輯為:把當前的DispacherServlet物件新增到從引數傳進來的ServletContext物件中,onStartup具體程式碼如下,

public final void onStartup(ServletContext servletContext) throws ServletException {
    String description = getDescription();
    if (!isEnabled()) {
        logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
        return;
    }
    // 註冊 Servlet 到 tomcat的 servletContext中
    register(description, servletContext);
}

​ 5.2 register()方法是具體註冊當前Servlet到tomcat的 servletContext中,程式碼如下,

@Override
protected final void register(String description, ServletContext servletContext) {
    // 新增當前的 servlet 到 tomcat 的 servletContext 中
    D registration = addRegistration(description, servletContext);
    if (registration == null) {
        logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
        return;
    }
    configure(registration);
}

​ 5.3 addRegistration方法是先獲取servlet名稱再註冊當前Servlet到引數傳進來的的servletContext引數中,程式碼如下

@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
    // 獲取 當前物件的 servlet 名稱
    String name = getServletName();
    //新增當前的 servlet 到 tomcat 的 servletContext 中
    return servletContext.addServlet(name, this.servlet);
}

​ 5.4總結一下,這個getSelfInitializer()很關鍵,主要邏輯為:把springmvc中的DispatcherServlet 新增到tomcat伺服器中的servletContext物件中 。

  1. getWebServer方法用來獲取WebServer,大概邏輯是:先建立Tomcat類,再初始化相關屬性,最後建立TomcatWebServer物件。具體的程式碼如下,
public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }
    // 建立具體的Tomcat類
    Tomcat tomcat = new Tomcat();
    // 獲取主路徑,並且設定主路徑
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    // 建立聯結器和協定
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    // 新增聯結器
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    // 設定 host 的自動部署屬性為 false
    tomcat.getHost().setAutoDeploy(false);
    // 設定 engine
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    // 初始化 TomcatEmbeddedContext ,並把 TomcatEmbeddedContext 新增至 host 中
	// 用 initializersToUse 初始化 TomcatStarter ,並設定 TomcatEmbeddedContext 的Starter屬性為  TomcatStarter 
    // initializersToUse 為函數式介面 ,即 實現了ServletContextInitializer介面 的匿名類
    prepareContext(tomcat.getHost(), initializers);
    // 建立具體的 TomcatwebServer
    return getTomcatWebServer(tomcat);
}
  1. prepareContext()方法是建立WebServer前的準備 ,主要邏輯為:建立 TomcatEmbeddedContext ,並把 TomcatEmbeddedContext 新增至 host 中,然後用 initializersToUse (initializersToUse 為實現了ServletContextInitializer介面 的集合,其中包括了getWebServer方法中的引數 initializers )初始化 TomcatStarter ,並設定 TomcatEmbeddedContext 的Starter屬性為TomcatStarter。即TomcatEmbeddedContext的Starter屬性為TomcatStarter。在tomcat啟動的時候會呼叫到TomcatStarter的onStartup方法。
  2. getTomcatWebServer()中會建立TomcatwebServer類,並且傳入 tomcat 和 埠是否大於等於0 兩個引數,程式碼如下
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    return new TomcatWebServer(tomcat, getPort() >= 0);
}

TomcatWebServer的建構函式中,除了給tomcat和 autoStart賦值,還會呼叫初始化方法initialize,TomcatWebServer的構造方法程式碼如下,

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    // 自動開啟
    this.autoStart = autoStart;
    // 初始化方法
    initialize();
}
  1. initialize()方法中會進行TomcatWebServer的初始,關鍵程式碼如下,
// Start the server to trigger initialization listeners
// tomcat 伺服器啟動
this.tomcat.start();

整個tomcat的啟動比較複雜,有興趣的可以去研究下tomcat原始碼,這裡不做多講直接給出結論。其中會呼叫上面建立的TomcatStarter類的onStartup方法,並傳入tomcat的servletContext物件。TomcatStarter的onStartup具體程式碼如下,

public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
    try {
        // 遍歷已經註冊的 initializers, 
        for (ServletContextInitializer initializer : this.initializers) {
            initializer.onStartup(servletContext);
        }
    }
    catch (Exception ex) {
        this.startUpException = ex;
        // Prevent Tomcat from logging and re-throwing when we know we can
        // deal with it in the main thread, but log for information here.
        if (logger.isErrorEnabled()) {
            logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
                         + ex.getMessage());
        }
    }
}

onStartup方法中會遍歷 initializers 集合並且呼叫其onStartup()方法,而initializers包括了我們在呼叫getWebServer時傳入的getSelfInitializer方法,該方法體的主要業務邏輯上面已經講了(請看本章的第5點),就是給傳進去的servletContext新增當前Spring容器中注入的SpringMVC的DispatchServlet類。

  1. 最後tomcat完成初始化,會監聽一個具體的埠。至此,tomcat啟動的過程已經把SpringMVC的DispatchServlet類新增到了tomcat的servletContext物件中。當請求進來Web伺服器的時候會轉到DispatchServlet中。DispatchServlet的整個初始化過程這裡不細講了,請參考 SpringMVC請求流程原始碼分析 文章。