1. 程式人生 > 其它 >【SpringBoot】原理分析(二):啟動流程原始碼分析(包括內嵌tomcat啟動分析)

【SpringBoot】原理分析(二):啟動流程原始碼分析(包括內嵌tomcat啟動分析)

技術標籤:Spring系列spring bootjavatomcat

看 SpringBoot 的啟動流程原始碼的入口很好找,就是啟動類的 SpringApplication.run(DemoApplication.class, args),點進run方法如下:

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
   return new SpringApplication(primarySources).run(args);
}

可以看到 SpringApplication.run 就幹了兩件事,一是建立 SpringApplication 物件,二是啟動 SpringApplication。

1.建立 SpringApplication 物件

SpringApplication 構造器如下:

public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources,
"PrimarySources must not be null"); // 儲存主配置類 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 儲存web應用的型別 // 注:分為響應式web應用,servlet型別web應用和非web應用,在後面用於確定例項化applicationContext的型別 this.webApplicationType = WebApplicationType.deduceFromClasspath
(); // 設定初始化器 [ApplicationContextInitializer型別],儲存到 SpringApplication中 // getSpringFactoriesInstances 作用是讀取spring.factories檔案key=ApplicationContextInitializer對應的value並例項化 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 設定監聽器[ApplicationListener型別],儲存到 SpringApplication中 // getSpringFactoriesInstances 作用是讀取spring.factories檔案key=ApplicationListener對應的value並例項化 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 儲存主配置類 // 沒啥特別作用,僅用於獲取入口類class物件 this.mainApplicationClass = deduceMainApplicationClass(); }

1.1 getSpringFactoriesInstances()

getSpringFactoriesInstances 是設定初始化器和監聽器的關鍵方法,用於去 META-INFO/spring.factories 中獲取 ApplicationContextInitializer 和 ApplicationListener 型別。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
   return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
   ClassLoader classLoader = getClassLoader();
   // Use names and ensure unique to protect against duplicates
   // 從類路徑的META-INF處讀取相應配置檔案spring.factories,然後進行遍歷,讀取配置檔案中Key(type)對應的value 
   Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    	       
   // 將names的物件例項化
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}

根據入參type型別 ApplicationContextInitializer.class 從類路徑的 META-INF 處讀取相應配置檔案spring.factories並例項化對應 Initializer。

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

和設定初始化器一個套路,通過getSpringFactoriesInstances函式例項化監聽器。

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

1.2 deduceEnvironmentClass()

查詢主配置類,查詢的依據就是看哪個方法是否有main方法

private Class<? extends StandardEnvironment> deduceEnvironmentClass() {
   switch (this.webApplicationType) {
   case SERVLET:
      return StandardServletEnvironment.class;
   case REACTIVE:
      return StandardReactiveWebEnvironment.class;
   default:
      return StandardEnvironment.class;
   }
}

上面分析完了 new SpringApplication(primarySources).run(args) 的第一步 new SpringApplication(),下面我們接著來看它是如何啟動這個 SpringBootApplication的。

2.啟動 SpringBootApplication

我們先來看看 run 方法,原始碼如下:

public ConfigurableApplicationContext run(String... args) {
   // 計時器
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
	
   // 建立一個容器
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

   configureHeadlessProperty();

   // 1).獲取監聽器集合物件
   SpringApplicationRunListeners listeners = getRunListeners(args);

   // 2).釋出容器 starting 事件(通過spring的事件多播器) 
   listeners.starting();

   try {
      // 封裝命令列引數 
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      
      // 3).根據掃描到的監聽器物件和函式傳入引數,進行環境準備。
      //    1:獲取或者建立環境  2:把命令列引數設定到環境中 3:通過監聽器釋出環境準備事件   
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
       
      // 列印springboot的圖示 
      Banner printedBanner = printBanner(environment);
      
	  // 4).建立容器 
      // 根據webApplicationType 來建立容器(通過反射建立)
      context = createApplicationContext();
		
	  // 獲取異常報告  
      // 和上面套路一樣,讀取spring.factories檔案key SpringBootExceptionReporter對應的value
      exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
		
	  // 5).context前置處理,準備上下文環境
	  //    1:把環境設定到容器中    2: 迴圈呼叫 AppplicationInitnazlier 進行容器初始化工作    
      //    3:釋出容器上下文準備完成事件    4:註冊關於springboot特性的相關單例Bean    5:釋出容器上下文載入完畢事件 
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		
      // 6).重新整理容器 
      // 和上面的一樣,context準備完成之後,將觸發SpringApplicationRunListener的contextPrepared執行
      refreshContext(context);
	  
      // 7).後置操作
      // 執行 ApplicationRunner 和 CommandLineRunner 
      afterRefresh(context, applicationArguments);

      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      // 8).釋出容器啟動事件
      listeners.started(context);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      // 出現異常;呼叫異常分析保護類進行分析 
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }
   try {
      // 釋出容器執行事件 
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

PS:關於上面核心8步的程式碼我後面再補,想了解的同學這裡可以先參考這篇文章

run 方法執行完畢,SpringBoot 專案就啟動好了。但是有一個問題我們還是有必要清楚的,內嵌 Tomcat 是如何啟動的?

3.內嵌Tomcat啟動流程

關於內嵌 Tomcat 可以看我的這篇文章 【Tomcat】第九篇:手寫嵌入式Tomcat外掛(超簡單)

tomcat 的啟動流程大致如下:

  1. org.springframework.boot.SpringApplication#refreshContext
  2. org.springframework.context.support.AbstractApplicationContext#refresh
  3. org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh
  4. org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer
  5. org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer

refreshContext()

private void refreshContext(ConfigurableApplicationContext context) {
   // 呼叫 AbstractApplication#refresh 方法,去初始化IOC容器
   refresh(context);
   // 註冊一個關閉容器時的鉤子函式,在jvm關閉時呼叫
   if (this.registerShutdownHook) {
      try {
         context.registerShutdownHook();
      }
      catch (AccessControlException ex) {
         // Not allowed in some environments.
      }
   }
}

refresh()

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      // 1.呼叫容器準備重新整理的方法,獲取容器的當時時間,同時給容器設定同步標識
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      // 2.告訴子類啟動refreshBeanFactory()方法,Bean定義資原始檔的載入從子類的refreshBeanFactory()方法啟動
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      // 3.為BeanFactory配置容器特性,例如類載入器、事件處理器等
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         // 4.為容器的某些子類指定特殊的BeanPost事件處理器
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         // 5.呼叫所有註冊的BeanFactoryPostProcessor的Bean
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         // 6.為BeanFactory註冊BeanPost事件處理器.BeanPostProcessor是Bean後置處理器,用於監聽容器觸發的事件
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         // 7.初始化資訊源,和國際化相關.
         initMessageSource();

         // Initialize event multicaster for this context.
         // 8.初始化容器事件傳播器.
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         // 9.呼叫子類的某些特殊Bean初始化方法
         onRefresh();

         // Check for listener beans and register them.
         // 10.為事件傳播器註冊事件監聽器.
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         // 11.初始化所有剩餘的單例Bean
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         // 12.初始化容器的生命週期事件處理器,併發布容器的生命週期事件
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         // 13.銷燬已建立的Bean
         destroyBeans();

         // Reset 'active' flag.
         // 14.取消refresh操作,重置容器的同步標識。
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         // 15.重設公共快取
         resetCommonCaches();
      }
   }
}

這裡我們繼續看 onRefresh 方法

onRefresh()

protected void onRefresh() throws BeansException {
   // For subclasses: do nothing by default. 
   // 給子類去實現
}

下面,我們開啟實現類 ServletWebServerApplicationContext 的 onRefresh 方法

protected void onRefresh() {
   super.onRefresh();
   try {
     // 建立嵌入式Servlet伺服器
     // 注:到這裡時已經建立好了SpringBoot應用上下文
      createWebServer();
   }
   catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}

createWebServer()

private void createWebServer() {
   // 獲取當前的WebServer
   WebServer webServer = this.webServer;
   // 獲取當前的ServletContext
   ServletContext servletContext = getServletContext();
   // 第一次進來,webServer和servletContext 預設都為null,會進入這裡
   if (webServer == null && servletContext == null) {
       // 獲取Servlet伺服器工廠
      StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
      // 工廠方法,獲取Servlet伺服器,並作為AbstractApplicationContext的一個屬性進行設定。
      // 該會建立DispatcherServlet物件,並新增到beanFactory中去,對應的beanName為dispatcherServlet
      ServletWebServerFactory factory = getWebServerFactory();
      createWebServer.tag("factory", factory.getClass().toString());
      // 這個方法為wrapper設定了servletClass為DispatcherServlet
      this.webServer = factory.getWebServer(getSelfInitializer());
      createWebServer.end();
      getBeanFactory().registerSingleton("webServerGracefulShutdown",
            new WebServerGracefulShutdownLifecycle(this.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);
      }
   }
   // 初始化一些ConfigurableEnvironment中的 ServletContext資訊
   initPropertySources();
}

getWebServer()

獲取 webServer 其實有多種選擇,SpringBoot 不止內嵌了 Tocmat,還內嵌了 Jetty 等。

這裡我們只看內嵌 tomcat,原始碼如下:

public WebServer getWebServer(ServletContextInitializer... initializers) {
    // 建立內嵌tomcat,直接new出來的
    Tomcat tomcat = new Tomcat();
    // 設定工作目錄
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory
        : createTempDir("tomcat");
    // 設定安裝目錄
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    // 初始化tomcat的聯結器
    Connector connector = new Connector(this.protocol);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    // 設定自動部署為false
    tomcat.getHost().setAutoDeploy(false);
    // 配置引擎
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    // 準備context
    prepareContext(tomcat.getHost(), initializers);
    return getTomcatWebServer(tomcat);
}


protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {  
    // 埠大於0啟動啟動  
    return new TomcatWebServer(tomcat, getPort() >= 0); 
}   
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    // 維護了一個tomcat的例項
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    // 初始化方法,啟動tomcat
    initialize();
}

// 初始化方法,啟動tomcat
private void initialize() throws WebServerException {
    logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
    synchronized (this.monitor) {
        try {
            addInstanceIdToEngineName();

            Context context = findContext();
            context.addLifecycleListener((event) -> {
                if (context.equals(event.getSource())
                    && Lifecycle.START_EVENT.equals(event.getType())) {
                    // Remove service connectors so that protocol binding doesn't
                    // happen when the service is started.
                    removeServiceConnectors();
                }
            });

            // Start the server to trigger initialization listeners
            // 啟動tomcat
            // 這裡面會為Wrapper設定servletClass為dispatcherServlet
            this.tomcat.start();

            // We can re-throw failure exception directly in the main thread
            rethrowDeferredStartupExceptions();

            try {
                ContextBindings.bindClassLoader(context, context.getNamingToken(),
                                                getClass().getClassLoader());
            }
            catch (NamingException ex) {
                // Naming is not enabled. Continue
            }

            // Unlike Jetty, all Tomcat threads are daemon threads. We create a
            // blocking non-daemon to stop immediate shutdown
            // 啟動一個守護程序進行等待,以免程式直接停止結束
            startDaemonAwaitThread();
        }
        catch (Exception ex) {
            stopSilently();
            throw new WebServerException("Unable to start embedded Tomcat", ex);
        }
    }
}

可以看到,內嵌 Tomcat 已經 start 了。