SpringBoot內嵌Tomcat的啓動過程理解

SpringBoot內嵌Tomcat的啓動過程理解

版本:SpringBoot:2.1.1.RELEASE Spring Framework:5.1.3 注意:不同版本實現有差異;

概述:Tomcat是如何在SpringBoot中啓動的,這一篇文件會進行簡單分析,關注主流程。

注意:

1.本文可能存在錯誤,建議IDE斷點調試,加以理解。配合各種圖形理解源碼,會更好。

2.閱讀源碼關注註釋行即可,不必追究每行代碼! 篇幅偏長,建議自行拆解。

3.建議對自動裝配、refresh() 啓動過程、getBean的創建過程有一定的理解。

4.本文僅做參考,如果想理解掌握,最好自己動手實踐。

5.Servlet 容器,就以Tomcat爲例子。


一、主導權的變化

1.1 SpringMVC + Spring

SpringMVC + Spring的時代,項目打包成war、部署到Tomcat這樣的Servlet容器中,由Servl容器啓動,伴隨着Listener和Servlet的生命週期回調、來創建Spring父子容器。不得不說Tomcat這樣的Servlet容器是具有主導地位的,即Servlet容器來驅動創建Spring容器。江湖地位不言而喻。

1.1 Tomcat 啓動,加載讀取Web.xml。容器裝載ContextLoaderListener和DispatcherServlet。

1.2 在此Tomcat具有主導地位。


1.2 SpringBoot

當SpringBoot來臨的時候,江湖地位發生顛覆性變化。Tomcat、以及相關組件等被封裝成爲Spring Bean。由Spring容器的生命週期來驅動Tomcat的創建、啓動。

1.1 Tomcat,由自身提供嵌入式相關API。SpringBoot 在Tomcat自身提供的嵌入式API的基礎上,進行了整合創新。注:Tomcat的嵌入核心能力,並非由SpringBoot提供。

1.2 Spring容器的生命週期來驅動Tomcat的創建啓動。

1.3 小結

上訴只是陳訴了目前的現狀,那麼SringBoot是如何讓Spring掌握主動權的,源碼見真知。


二、源碼見真知

閱讀源碼過程建議配合斷點調試,所謂紙上得來終覺淺,絕知此事要躬行!

關於tomcat的的啓動和創建大致如下(忽略分支判斷等情況,關注主流程)

在這裏插入圖片描述

注意:factory.getWebServer(getSelfInitializer()); 是核心方法;裏面涵蓋了大量的邏輯。包括Tomcat的創建、初始化等一系列操作。(單獨會講解)

WebServer對象是對Tomcat、Jetty等的門面包裝。


2.1 啓動

模板模式,是Spring中非常常用的設計模式。AbstractApplicationContext中refresh() 提供了模板方法,子類覆蓋,從而實現擴展。

在AbstractApplicationContext的refresh()中,存在一個onRefresh,這個方法留個了子類擴展。所以Tomcat就在這裏創建,注:這個時候所有的BeanDefinition已經被加載的容器中。僅僅是部分BeanDefinition還沒進行初始化(注意:是BeanDefinition哦);在這個地方創建Tomcat是不錯的選擇。

//AbstractApplicationContext
@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
		……
		// Initialize other special beans in specific context subclasses.
		 onRefresh(); //子類擴展。在ServletWebServerApplicationContext#createWebServer
		……
	}

通過斷點得到了目標:ServletWebServerApplicationContext

	//ServletWebServerApplicationContext
    @Override
	protected void onRefresh() {
		super.onRefresh(); //先調用父類的onRefresh,再額外創建內嵌的Servlet容器
		try {
			createWebServer(); //創建內嵌ServletWebServer
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}

執行這個方法時,當前應用上下文中的所有BeanDefinition都已經加載,且對Bean進行初始化的相關組件如:BeanFactoryPostProcessors和BeanPostProcessor也都已經初始化完成。也即整個Bean創建的生命週期中所需要的組件都準備就緒,此時無論需要使用任何Bean。都可以完整可靠地創建出來。

BeanFactory可以將BeanDefinition轉換成你想要的Bean。可以通過name、Type等多種方式獲取。這也是BeanFactory容器強大的好處。


2.2 createWebServer

ServletWebServerApplicationContext 中創建WebServer。在這一步驟完成了tomcat的創建和初始化工作。

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) { //一開始爲空。後續完成
        //得到Servelt工廠
        ServletWebServerFactory factory = getWebServerFactory();
        //傳入參數ServletContextInitializer
        //在ServletContainer初始化完成以後,會調用其onStartup方並傳入
        this.webServer = factory.getWebServer(getSelfInitializer());  
    }
    else if (servletContext != null) {
        try {
            //如果webServer和servletContext 都存在的情況
            //那什麼情況會出現呢?
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    //初始化PropertySource,用於使用Servlet
    initPropertySources();
}

getSelfInitializer 只有等Servlet容器初始化完成的時候執行。這個方法會註冊一些Servlet上下文特有的組件和作用域。同時會獲取 ApplicationContext中所有

ServletContextInitializer 類型的Bean。統一調用他們的onStartup方法。

預告:getSelfInitializer() 創建一個org.springframework.boot.web.servlet.ServletContextInitializer匿名類,而onStartup指向 selfInitialize(ServletContext servletContext)

在 selfInitialize(ServletContext servletContext) 中實現了Servlet註冊到ServletContext 的功能。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Xcv6ZnEr-1592493773886)(./img/20200618223842.png)]

上圖通過執行表達式,得到了多個RegistrationBean。其中有一個就是DispatcherServelet的包裝對象。


2.3 getWebServerFactory

創建ServletWebServerFactory 涉及到過程比較複雜,包括自動裝配、ServletWebServerFactory的一些定製初始化過程。如下圖所示:

  1. 創建ServletWebServerFactory對象

  2. 此過程會經歷後處理器的中方法的處理。例如WebServerFactoryCustomizerBeanPostProcessor,在這個過程中,會遍歷註冊的WebServerFactoryCustomizer集合

  3. 在這個過程中,會對ServletWebServerFactory做一些配置信息的處理。這些配置信息來源用戶自定義,例如server.port端口信息等

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6EKCnasD-1592493773890)(./img/20200618172844.png)]

藉助BeanFactory 獲取ServletWebServerFactory類型的ServletWeb工廠

	protected ServletWebServerFactory getWebServerFactory() {
        //從BeanFactory中搜索ServletWebServerFactory類型
		String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
		if (beanNames.length == 0) {
			throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
					+ "ServletWebServerFactory bean.");
		}
		if (beanNames.length > 1) {
			throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
					+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
		}
		return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class); //過程參考上圖,Bean的創建過程遠遠比上圖複雜。
	}

默認web環境就是TomcatServletWebServerFactory。不得不說,創建ServletWebServerFactory對象和對ServletWebServerFactory對象做屬性設置是分開的兩個過程。WebServerFactoryCustomizerBeanPostProcessor使這兩個過程不耦合,從而增加擴展能力!不過這麼玩還是蠻複雜的喲!


2.4 ServletWebServerFactory 來源

ServletWebServerFactory的來源和創建是什麼時候呢; 在默認情況下,ServletWebServerFactory接口的實現類有三個,其中一些公關方法被提取到抽象父類中。

接下來以TomcatServletWebServerFactory爲例,探索Servlet容器工廠Bean的註冊過程

,在SpringBoot中採用了大量的自動裝配,而ServletWebServerFactory的實現也利用了自動裝配的特性。當然,這個類還不是以AutoConfiguration結尾的類,所以可能還有其他類做應道。一般是XXXAutoConfiguration。(自動裝配能力,利用SPI、@Import、Conditional等實現條件自動裝配)

@Configuration
class ServletWebServerFactoryConfiguration {

	@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class }) //同時具有Servlet和Tomcat兩個類的時候
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) //不存在此ServletWebServerFactory類型的class
	public static class EmbeddedTomcat {

		@Bean
		public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
			return new TomcatServletWebServerFactory();
		}

	}
	…… //剩餘的容器工廠創建

}

ServletWebServerFactoryConfiguration類的 引入,則依託SpringBoot的autoconfigure包,這個包下面的ServletWebServerFactoryAutoConfiguration提供了對ServletWebServerFactoryConfiguration引入。這是自動裝配的規則屬性。

注:關注註釋行即可

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) //優先級最高
@ConditionalOnClass(ServletRequest.class)       // ServletRequest類存在
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class) //開啓ServerProperties屬性,用於配置server相關的。例如端口號
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, //導入BeanPostProcessors類
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

    //用於ServerProperties 對ServletWebServerFactory 定製化
	@Bean
	public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
		return new ServletWebServerFactoryCustomizer(serverProperties);
	}

    //Tomcat 存在;使用ServerProperties 定製。例如端口信息等
	@Bean
	@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
	public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
			ServerProperties serverProperties) {
		return new TomcatServletWebServerFactoryCustomizer(serverProperties);
	}

	/**註冊WebServerFactoryCustomizerBeanPostProcessor。
	 * Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
	 * {@link ImportBeanDefinitionRegistrar} for early registration.
	 */
	public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
		……
		@Override
		public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
				BeanDefinitionRegistry registry) {
			if (this.beanFactory == null) {
				return;
			}
            //註冊WebServerFactoryCustomizerBeanPostProcessor,用於加載WebServerFactoryCustomizer
            //對 WebServerFactoryCustomizer 進行個性化設置
			registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
					WebServerFactoryCustomizerBeanPostProcessor.class);
			registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
					ErrorPageRegistrarBeanPostProcessor.class);
		}
        ……
	}
}
//這個方法在Bean的初始化過程中被調用。用於對ServeletWebServerFactory進行自定義
@FunctionalInterface
public interface WebServerFactoryCustomizer<T extends WebServerFactory> {
	void customize(T factory);

}

2.5 factory.getWebServer

得到一個Tomcat的容器。實現了創建並啓動.

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    Tomcat tomcat = new Tomcat(); //創建Tomcat實例
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false); //不自動部署
    configureEngine(tomcat.getEngine());   // 配置Tomcat的引擎。
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    //準備Tomcat的StandardContext,並添加到Tomcat中,同時把initializers 註冊到類型爲
    //TomcatStarter的ServletContainerInitializer中
    prepareContext(tomcat.getHost(), initializers);
    //將創建好的Tomcat。包裝成WebServer返回,設計對initializers#onStartup的回調。啓動了Tomcat的start方法,做初始化操作
    return getTomcatWebServer(tomcat);
}

當然Tomcat還沒有真正打印端口,算不上結束


2.6 啓動

打印啓動Tomcat啓動日誌,在這個地方最終實現tomcat的啓動流程。

#ServletWebServerApplicationContext#finishRefresh 	
@Override
    protected void finishRefresh() {
    super.finishRefresh();
    WebServer webServer = startWebServer(); //啓動容器。
    if (webServer != null) {
        publishEvent(new ServletWebServerInitializedEvent(webServer, this)); //發佈事件ServletWebServerInitializedEvent
    }
}
	private WebServer startWebServer() {
		WebServer webServer = this.webServer;
		if (webServer != null) {
			webServer.start(); //開始啓動容器了
		}
		return webServer;
	}

2.7 小結

下面總結一下ServerletWebServer的註冊、創建與啓動的過程。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-qF3LSpPk-1592493773896)(./img/20200618173508.png)]

當然,getSelfInitializer()調用細節涉及DispatcherServlet的裝載,在此進行簡單說明!


三、DispatcherServlet裝載

DispatcherServlet的創建和註冊;通過getSelfInitializer() 方法去請求相關的ServletContextInitializer組件,讓後調用他們的onStartup方法,實現往ServletContext註冊組件功能

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-J9tqBxyO-1592493773902)(./img/DispatcherServlet.png)]

分析一下:ServletContextInitializer的組件的起源吧


3.1 自動裝配

關於DispatcherServlet 這個Bean的裝配與ServletWebServerFactory的方式如出一轍,都是使用自動裝配的結果。

關注註釋行代碼

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)  //條件DispatcherServlet 存在
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) //晚於ServletWebServerFactoryAutoConfiguration
public class DispatcherServletAutoConfiguration {

	public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
	public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

	@Configuration
	@Conditional(DefaultDispatcherServletCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class }) //WebMvcProperties 配置DispatcherServlet
	protected static class DispatcherServletConfiguration { //關於DispatcherServlet的配置類

		……
		//名稱爲dispatcherServlet的類
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServlet dispatcherServlet() {
			DispatcherServlet dispatcherServlet = new DispatcherServlet(); //創建並配置一些必要信息
			dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());
			dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());
			dispatcherServlet
					.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
			dispatcherServlet.setEnableLoggingRequestDetails(this.httpProperties.isLogRequestDetails());
			return dispatcherServlet;
		}	
        ……
	}
	
    //下面這個類讓DispatcherServlet註冊到Tomcat容器的關鍵類。
	@Configuration
	@Conditional(DispatcherServletRegistrationCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties(WebMvcProperties.class)
	@Import(DispatcherServletConfiguration.class)
	protected static class DispatcherServletRegistrationConfiguration {

		……
		//dispatcherServletRegistration
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
		@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
            //傳入DispatcherServlet。 構造一個DispatcherServletRegistrationBean。
			DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
					this.webMvcProperties.getServlet().getPath());
			registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); //名稱
			registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup()); //配置啓動是否記載
			if (this.multipartConfig != null) {
				registration.setMultipartConfig(this.multipartConfig);
			}
			return registration;
		}

	}
	……

	}
}

DispatcherServletRegistrationBean 這個類包裝了DispatcherServlet。 而DispatcherServletRegistrationBean 作爲ServletContextInitializer 子類,會在Tomcat啓動的時候進行回調。


3.2 RegistrationBean

基於Servlet3.0的註冊的Bean的基類。RegistrationBean的實現類有Servlet、Filter、Listener ;三種組件。他們的實現方式都差不多一個樣

想ServletContext註冊組件的操作就在RegistrationBean以及子類中。

在DispatcherServletRegistrationBean 父類ServletRegistrationBean#addRegistration 實現了Servlet的添加工作。

public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> {
    ……
    //添加Servlet到ServletContext的操作    
    @Override
    protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
        String name = getServletName();
        return servletContext.addServlet(name, this.servlet); //註冊組件
    }
}

3.3 ServletContextInitializer 調用鏈條

關於ServletContextInitializer的調用如下:

在這個類繼承中,使用了模板模式,在父類中定義了抽象方法,在子類中實現某個具體的註冊組件。在Servlet中,最終會調用ServletRegistrationBean的addRegistration方法實現組件的註冊。


3.4 整體流程如下

  1. 通過自動裝配,引入DispatcherServlet。然後被封裝成ServletRegistrationBean,註冊到ServletWebServer中
  2. 在Tomcat創建階段,把 getSelfInitializer() 傳入到TomcatStarter中
  3. 在TomcatStarter啓動是會調用 getSelfInitializer()的onStartup() 方法
  4. 在getSelfInitializer() 中會獲取所有 ServletContextInitializer的類型的Bean、調用他們的onStartup方法。
  5. RegistrationBean的onStartup方法就在此進行調用。實現Servlet、Filter、Listener註冊到ServletContext的過程。

下圖是其大致的調用鏈條:

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
}

//會在TomcatStarter進行調用。將RegistrationBean的onStartup進行調用
//從而註冊各個組件到ServletContext中
private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareWebApplicationContext(servletContext);
    registerApplicationScope(servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext); // 註解註冊過程
    }
}

四、總結

1.本篇主要談了Tomcat容器在SpringBoot的創建流程。其中涉及的很多分支並未進行詳細探索,感興趣的可以自行研究。

2.關於自動裝配的功能、Tomcat的啓動細節等未做深入理解。

3.閱讀源碼建議配合各種圖形來解決,從整體上去理解,避免陷入舍本逐末。

4.文章中可能會存在一些bug,建議結合源碼斷點調試進行理解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章