Spring Boot內嵌Tomcat原理

互聯網高級架構師 發佈 2022-07-04T06:49:20.218816+00:00

一、ServletWebServerFactoryConfiguration實際上,spring boot是指出多種伺服器啟動的,並不只是tomcat,還有jetty等。

一、servletWebServerFactoryConfiguration

實際上,spring boot是指出多種伺服器啟動的,並不只是tomcat,還有jetty等。因此我們可以猜測具體哪種伺服器是可以配置的,而spring boot又是以自動配置聞名,那麼這些伺服器肯定與某些自動配置類相關。

實際上,spring boot的servlet web伺服器的配置類就是位於Spring-boot-autoconfigure.jar下的/META-INF/spring.factories文件中的一個名為ServletWebServerFactoryAutoConfiguration的自動配置類。

在該自動配置類中,會通過@Import想容器中注入四個配置類,我們可以看到,各種容器的web服務配置,Tomcat、Jetty、Undertow,其中Tomcat對應EmbeddedTomcat。

這個EmbeddedTomcat配置類又會向Spring容器注入TomcatServletWebServerFactory,這個類就是Tomcat啟動的關鍵類,用於創建TomcatWebServer。

另外,ServletWebServerFactoryAutoConfiguration中還會注入一系列的Customizer,用於修改內嵌Tomcat的參數和配置。

二、onRefresh啟動web服務

那麼,這個TomcatServletWebServerFactory是怎麼在什麼時候被加載到容器中並使用的呢?Tomcat又是什麼時候被啟動的呢?

之前的文章就講過,在spring boot容器啟動過程中,在創建容器之後,會執行刷新容器的操作,也就是refresh()操作,這個操作實際上就是spring容器的啟動方法,將會加載Bean以及各種配置。該方法是spring項目的核心方法,源碼非常多,我們在之前以及花了大量時間講過了,在此不再贅述,之前的文章連結Spring IoC容器初始化源碼。

在refresh()方法中,有一個onRefresh()方法。

這個onRefresh方法默認是一個空的實現,這是留給子類容器實現的擴展方法。這個方法是在所有的bean定義被注入到容器中之後調用的,而在onRefresh方法之後,則會對所有的普通單例bean進行實例化和初始化。

默認的web服務容器是AnnotationConfigServletWebServerApplicationContext,它又繼承了ServletWebServerApplicationContext,該類就對onRefresh方法進行了實現,並且Spring boot的web伺服器就是在此啟動的!

/**
 * ServletWebServerApplicationContext實現的方法
 */
@Override
protected void onRefresh(){
   //調用父類的邏輯
   super.onRefresh();
   try{
      /*
       * 關鍵方法,創建WebServer
       */
      createWebServer();
   }
   catch( Throwable ex ){
      throw new ApplicationContextException( "Unable to start web server", ex );
   }
}

可以看到,內部調用了createWebServer方法創建web伺服器。

2.1 createWebServer創建web服務

createWebServer方法的代碼如下,它會通過之前配置的ServletWebServerFactory,獲取webServer,即創建web伺服器。

一般我們使用的ServletWebServerFactory就是TomcatServletWebServerFactory,使用的webserver就是TomcatWebServer。

在創建了webserver之後,會想容器注入兩個SmartLifecycle類型的bean實例,這實際上是一個擴展點的實例,用於實現容器回調。

其中,註冊的WebServerStartStopLifecycle實例,在ServletWebServerApplicationContext類型的容器啟動完畢後會調用該實例的start方法啟動webServer並發送事件,在ServletWebServerApplicationContext類型的容器銷毀時將會調用該實例的stop方法銷毀webServer。

private volatile WebServer webServer;

/**
 * ServletWebServerApplicationContext的方法
 * <p>
 * 創建web服務
 */
private void createWebServer(){
   //獲取WebServer,這裡默認是空的
   WebServer webServer = this.webServer;
   //獲取servletContext,即servlet上下文,這裡默認是空的
   ServletContext servletContext = getServletContext();
   /*
    * 獲取webServer,初始化web服務
    */
   if( webServer == null && servletContext == null ){
      //獲取web服務工廠,默認就是TomcatServletWebServerFactory
      ServletWebServerFactory factory = getWebServerFactory();
      /*
       * 通過web服務工廠獲取web服務,核心代碼
       * 創建內嵌的Tomcat並啟動
       */
      this.webServer = factory.getWebServer( getSelfInitializer() );
      /*
       * 註冊WebServerGracefulShutdownLifecycle的實例到容器中
       * ReactiveWebServerApplicationContext容器啟動完畢後會調用該實例的start方法
       * ReactiveWebServerApplicationContext容器銷毀時將會調用該實例的stop方法
       */
      getBeanFactory().registerSingleton( "webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle( this.webServer ) );
      /*
       * 註冊WebServerStartStopLifecycle的實例到容器中
       * ServletWebServerApplicationContext容器啟動完畢後會調用該實例的start方法嘗試啟動webServer並發送事件
       * ServletWebServerApplicationContext容器銷毀時將會調用該實例的stop方法銷毀webServer
       */
      getBeanFactory().registerSingleton( "webServerStartStop", new WebServerStartStopLifecycle( this, this.webServer ) );
   }
   else if( servletContext != null ){
      try{
         getSelfInitializer().onStartup( servletContext );
      }
      catch( ServletException ex ){
         throw new ApplicationContextException( "Cannot initialize servlet context", ex );
      }
   }
   //初始化ConfigurableWebEnvironment類型的配屬數據源
   initPropertySources();
}

getWebServerFactory獲取web服務工廠

該方法獲取web服務工廠,工廠用於創建web服務。

/**
 * ServletWebServerApplicationContext的方法
 * <p>
 * 獲取ServletWebServerFactory,用於初始化webServer
 * 默認返回TomcatServletWebServerFactory
 */
protected ServletWebServerFactory getWebServerFactory(){
   //從容器中搜索ServletWebServerFactory類型的beanName數組
   //之前的ServletWebServerFactoryConfiguration配置類就會像容器中
   //注入ServletWebServerFactory的bean,默認就是TomcatServletWebServerFactory
   String[] beanNames = getBeanFactory().getBeanNamesForType( ServletWebServerFactory.class );
   //沒有web服務工廠
   if( beanNames.length == 0 ){
      throw new ApplicationContextException(
            "Unable to start ServletWebServerApplicationContext due to missing " + "ServletWebServerFactory bean." );
   }
   //有多個web服務工廠
   if( beanNames.length > 1 ){
      throw new ApplicationContextException(
            "Unable to start ServletWebServerApplicationContext due to multiple " + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(
                  beanNames ) );
   }
   //從容器中獲取web服務工廠的實例
   return getBeanFactory().getBean( beanNames[ 0 ], ServletWebServerFactory.class );
}

getWebServer獲取web服務

ServletWebServerFactory的方法,用於獲取web服務。其中TomcatServletWebServerFactory的方法用於創建Tomcat實例並返回TomcatServer。

該方法中的一些名詞比如baseDir、connector、Service、Host、AutoDeploy 、Engine等等都是Tomcat中的概念,我們之前就介紹過了,在此不再贅述了。

在最後的getTomcatWebServer方法中會對Tomcat伺服器進行啟動。控制台會輸出日誌:Tomcat initialized with port(s): 8080 (http)。

/**
 * TomcatServletWebServerFactory的方法
 * 創建內嵌的Tomcat
 *
 * @param initializers 初始化器
 * @return Tomcat的web服務
 */
@Override
public WebServer getWebServer( ServletContextInitializer... initializers ){

   if( this.disableMBeanRegistry ){
      Registry.disableRegistry();
   }
   //創建Tomcat實例
   Tomcat tomcat = new Tomcat();
   //設置Tomcat的基本目錄
   File baseDir = ( this.baseDirectory != null ) ? this.baseDirectory : createTempDir( "tomcat" );
   tomcat.setBaseDir( baseDir.getAbsolutePath() );
   //設置Connector,用於接受請求發揮響應
   Connector connector = new Connector( this.protocol );
   connector.setThrowOnFailure( true );
   tomcat.getService().addConnector( connector );
   //自定義連接器
   customizeConnector( connector );
   tomcat.setConnector( connector );
   //是否自動部署
   tomcat.getHost().setAutoDeploy( false );
   //設置Engine
   configureEngine( tomcat.getEngine() );
   //自己擴展的連接器
   for( Connector additionalConnector : this.additionalTomcatConnectors ){
      tomcat.getService().addConnector( additionalConnector );
   }
   //準備上下文
   prepareContext( tomcat.getHost(), initializers );
   //創建TomcatWebServer,啟動Tomcat,返回TomcatWebServer
   return getTomcatWebServer( tomcat );
}

Tomcat啟動後的繼續執行Spring的邏輯,初始化bean實例等等,Spring容器初始化完畢之後,調用WebServerStartStopLifecycle的start方法,對TomcatWebServer進行啟動,此時控制台會輸出日誌:Tomcat started on port(s): 8080 (http) with context path ''。

作者:劉Java
連結:https://juejin.cn/post/7114679361042645022
來源:稀土掘金

關鍵字: