SpringBoot原始碼學習4——SpringBoot內嵌Tomcat啟動流程原始碼分析

2023-05-15 06:01:00

系列文章目錄和關於我

零丶引入

我在初學spring的時候,很懵逼,因為整個專案中不存在main方法,讓我有點摸不著頭腦。那時候我知道有個東西叫tomcat是它監聽了埠,解析了協定調到了我的servlet。

在我初學SpringBoot的時候,很懵逼,有main方法了,但是tomcat在哪裡呢,又是如何啟動起來的?。

一丶原生tomcat啟動流程

Tomcat總體架構,啟動流程與處理請求流程中學習了tomcat總體架構和啟動流程。

在springboot內嵌tomcat中則不再使用BootStrap->Catalina這種方式進行啟動,而是跨過這一層直接啟動了Server。

二丶SpringBoot根據上下文推斷ApplicationContext型別

下圖是SpringBoot的啟動流程,其中紅色框框是SpringBoot推斷ApplicationContext的步驟

這是我們學習springboot內嵌tomcat啟動的切入點。

1.推斷當前webApplication型別

在springboot啟動的時候會根據classpath推斷當前web應用型別。如果在classpath中存在org.springframework.web.reactive.DispatcherHandler那麼會視為REACTIVE型別,如果存在javax.servlet.Servlet,org.springframework.web.context.ConfigurableWebApplicationContext那麼會視為SERVLET型別

2.獲取webApplication型別對應ApplicationContext

SpringBoot會使用ApplicationContextFactory去構建出一個ApplicationContext,一般預設使用DefaultApplicationContextFactory,DefaultApplicationContextFactory會從spring.factories中找出所有的ApplicationContextFactory實現類根據webApplication型別去建立

其中有SERVLET型別對應的AnnotationConfigServletWebServerApplicationContext內部類Factory和REACTIVE型別對應的AnnotationConfigReactiveWebServerApplicationContext內部類Factory。

在不使用spring 響應式程式設計的情況下這裡都會使用AnnotationConfigServletWebServerApplicationContext內部類Factory去構建

也就是說SpringBoot的啟動會使用SpringApplication構建出AnnotationConfigServletWebServerApplicationContext最為Spring上下文。

三丶AnnotationConfigServletWebServerApplicationContext重新整理觸發tomcat啟動

在SpringBoot啟動的過程中,SpringApplication會觸發Spring上下文(ApplicationContext,也就是AnnotationConfigServletWebServerApplicationContext)的重新整理

AnnotationConfigServletWebServerApplicationContext是AbstractApplicationContext的子類,其refresh重新整理方法由AbstractApplicationContext進行了實現。大致流程如下

其中onRefresh勾點方法便會觸發Tomcat的啟動,onRefrsh由 AnnotationConfigServletWebServerApplicationContext父類別ServletWebServerApplicationContext進行了實現

四丶createWebServer建立web伺服器

下圖是SpringBoot啟動web伺服器的全流程

1.ServletWebServerFactory

WebServerFactory是一個標記介面,ServletWebServerFactory中定義了方法getWebServer(ServletContextInitializer... initializers)來建立WebServer,如參ServletContextInitializer是函數式介面,具備方法onStartup來進行回撥。

預設設定下SpringBoot將使用TomcatServletWebServerFactory來建立WebServer。

2.WebServer

WebServer是springboot對伺服器的抽象,具備start,stop,getPort,shutDownGracefully(優雅停)方法。

3.建立TomcatWebServer流程

組裝生成TomcatServer主要依賴於Tomcat這個類,Tomcat是嵌入式tomcat啟動器,提供眾多api來組裝tomcat伺服器。這一步其實就是在組裝tomcat容器模型。

可以看到Tomcat中具備Server屬性,這代表了web應用伺服器

在Server中具備Service陣列屬性表示web應用伺服器中眾多的服務

這裡只有一個服務——StandardService,其中包含Connector陣列和Engine陣列

  • Connector

    Connector負責監聽使用者端請求,返回響應資料。

    Connector內部由ProtocalHandler屬性,其使用AbstractEndpoint監聽埠,並將請求交給Processor處理

  • Engine

    Engine負責處理具體的請求。

    Engine是一個Container內部使用HashMap維護Host(key是hostName,value是Host物件)

    Host也是一個Container,內部同一使用HashMap維護Context(key是上下文名稱,value是Context物件)

    最終將設定Tomcat物件到TomcatWebServer屬性上,並呼叫Tomcat#start啟動Tomcat伺服器

五丶TomcatWebServer啟動Tomcat

TomcatWebServer會呼叫Tomcat.start方法來啟動Tomcat,整個啟動流程和原生Tomcat一致

六丶DispatcherServlet是怎麼被加到tomcat中的

SpringBoot中如果使用web-stater,那麼會引入DispatcherServlet的自動裝配,這也就是為什麼Tomcat接收到的請求會來到DispatcherServlet,然後由DispatcherServlet反射呼叫到Controller的方法。

那麼DispatcherServlet是什麼時候被加入到Tomcat中的呢?

TomcatReactiveWebServerFactory#configureContext方法中會註冊TomcatStarterTomcatEmbeddedContext

TomcatEmbeddedContextStandardContext的子類,在TomcatEmbeddedContext被呼叫start的時候,會拿出所有的ServletContainerInitializer呼叫其onStartup

這裡便會拿到TomcatStarterTomcatStarter使用ServletContextInitializer陣列記錄了ServletWebServerApplicationContext#selfInitialize方法,從而實現ServletWebServerApplicationContext#selfInitialize的回撥

SpringBoot之所以這麼做是因為ServletContainerInitializer是Servlet規範介面,而ServletContextInitializerSpringBoot定義的介面,利用TomcatStarter將SpringBoot定義的介面嫁接到Servlet定義的規範中從而保證當用戶將SpringBoot打包成war包也能觸發ServletWebServerApplicationContext#selfInitialize

那麼ServletWebServerApplicationContext#selfInitialize做了什麼

private void selfInitialize(ServletContext servletContext) throws ServletException {
    // <1> 將當前 Spring 應用上下文設定到 ServletContext 上下文的屬性中
    // 同時將 ServletContext 上下文設定到 Spring 應用上下文中
    prepareWebApplicationContext(servletContext);
    // <2> 向 Spring 應用上下文註冊一個 ServletContextScope 物件(ServletContext 的封裝)(這就是application這種bean作用域生效的本原因)
    registerApplicationScope(servletContext);
    // <3> 向 Spring 應用上下文註冊 `contextParameters` 和 `contextAttributes` 屬性
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    /**
     * <4> 【重點】先從 Spring 應用上下文找到所有的 ServletContextInitializer
     * 也就會找到各種  RegistrationBean,然後依次呼叫他們的 `onStartup` 方法,向 ServletContext 上下文註冊 Servlet、Filter 和 EventListener
     * 例如 DispatcherServletAutoConfiguration DispatcherServletRegistrationBean 就會註冊 @link DispatcherServlet 物件
      * 所以這裡執行完了,也就啟動了 Tomcat,同時註冊了所有的 Servlet,那麼 Web 應用準備就緒了
     */
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}

Spring會從BeanFactory拿到所有ServletContextInitializer的實現

註冊其實都是回撥這些ServletContextInitializer的onStartup方法,DispatcherServletRegistrationBean則會將DispatcherServlet註冊到ServletContext中。

TomcatEmbeddedContext會將DispatcherServlet保證成Wrapper加入到TomcatEmbeddedContext中去。

七丶總結

這一波學習,讓我深刻的理解了Tomcat容器模型,也瞭解到SpringBoot中使用Filter或者Servlet的時候,為什麼要向Spring注入對應的RegsitrationBean,因為只有這樣ServletWebServerApplicationContext才能從容器中獲取到RegsitrationBean並註冊到Tomcat中。