1. 程式人生 > >一、【徹底精通Spring Boot2原始碼】 SpringApplication.run() 啟動流程原始碼徹底剖析

一、【徹底精通Spring Boot2原始碼】 SpringApplication.run() 啟動流程原始碼徹底剖析

       本文是對spring boot2 啟動整個流程進行原始碼解析,也是我發表springboot系列的第一篇文章,解析的過程我會一行一行解讀,網上很多的解析案例都是貼一段原始碼,然後說幾句概況性的語句,看了之後也是丈二和尚 摸不到頭腦,既然解析原始碼,我們就儘量徹底理解每一行是做什麼的,有什麼作用。

  下面我們來看原始碼,走進啟動模式;

SpringApplication.run(BootApplication.class, args);

  啟動類開始執行;

new SpringApplication(primarySources).run(args);

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

先走進SpringApplication的構造方法,new一個SpringApplication例項,然後執行run方法。run方法執行結束之後整個啟動流程就是走完了。

1、SpringApplication 的建構函式,建立SpringApplication物件,一下是建構函式實現的具體初始化過程。


    //使用的資源載入器
   
    //主要的bean資源 primarySources【在這裡是啟動類所在的.class】,不能為null,如果為null,拋異常
   
    /**
    * primarySources 是Set<Class<?>> 型別,把啟動類(我這裡是定義為BootApplication)
    * .class (Class<?> 該類的例項陣列轉化成list,放在LinkedHashSet集合中)
    */


 
    /**
    * deduceWebApplicationType() = SERVER
    * 應用程式應該作為基於servlet的web應用程式執行,並且應該啟動一個
    * 嵌入式servlet web伺服器。
    */

  
     /**
      * 使用類載入機制,和發射實現對ApplicationContextInitializer 介面熟悉的初始化。
      * ApplicationContextInitializer是Spring IOC一個回撥介面,它會在ConfigurableApplicationContext的refresh()
      * 方法呼叫之前被呼叫,做一些容器的初始化工作。每一個initailizer都是一個實現了ApplicationContextInitializer介面的例項。
      * springboot中一共有6個實現類,後面遇到再分析各個實現類初始化的作用。
      */

   
    /**  使用類載入機制,和發射實現對ApplicationListener(事件監聽容器) 介面熟悉的初始化。
        當用Spring ApplicationContext註冊時,事件將相應地過濾,並呼叫偵聽器以匹配事件物件。
     */

   
    /**  指定main函式啟動所在的類,即啟動類BootApplication.class
   
 

2、啟動執行Spring應用程式,建立並重新整理一個新應用application程式

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			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;
	}

 

      /**
            StopWatch: 簡單的秒錶,允許定時的一些任務,公開每個指定任務的總執行時間和執行時間。
            這個物件的設計不是執行緒安全的,沒有使用同步。不會作為生產環境中使用,二此處因為啟動
            SpringApplication是在單執行緒環境下,使用安全。
        */

       
        /** 設定當前啟動的時間為系統時間startTimeMillis = System.currentTimeMillis();*/
       
        /** 系統設定headless模式
        (是一種系統配置,其中顯示裝置、鍵盤或滑鼠缺乏。聽起來出乎意料,但實際上您可以在這種模式下執行不同的操作,即            使是使用  圖形資料。)
           想進一步瞭解的訪問(https://www.oracle.com/technetwork/articles/javase/headless-136834.html)
        */

      

/** 通過類載入機制和反射建立SpringApplicationRunListeners例項SpringApplicationRunListeners:在建立和更新ApplicationContext 方法前後分別觸發回撥的形式呼叫了listeners物件的started方法和finished....等等方法, 並在建立和重新整理ApplicationContext的不同階段,呼叫listeners的相應方法以執行操作。所以,SpringApplicationRunListeners實際上就是在SpringApplication 物件的run方法執行的不同階段,去監聽回撥一些操作,並且這些操作是可配置的。  通過spring.factories中發現SpringApplicationRunListeners 載入的是org.springframework.boot.context.event.EventPublishingRunListener是 SpringApplicationRunListener的實現類.(SpringApplicationRunListeners的內部儲存了SpringApplicationRunListener的集合,提供了SpringApplicationRunListener一樣方  法,方便統一遍歷呼叫所有SpringApplicationRunListener。)
        */

       
        /** 開始監聽指定的一些event事件,檢視原始碼springboot中應該是有45處會觸發回撥*/
      

      
        
            /** 提供對用於執行SpringApplication的引數的訪問。取預設實現*/
      
            /**
                構建容器環境
            */

     
             /**
                對環境中一些bean忽略配置
            */    

     
            /**
            * 日誌控制檯列印設定
            * /

      
            /**
                建立容器
            */

      
            /**
                追蹤原始碼prepareContext()進去我們可以發現容器準備階段做了下面的事情:
                容器設定配置環境,並且監聽容器,初始化容器,記錄啟動日誌,
                將給定的singleton物件新增到此工廠的singleton快取中。
                將bean載入到應用程式上下文中。
            */

         
            /**
                1、同步重新整理,對上下文的bean工廠包括子類的重新整理準備使用,初始化此上下文的訊息源,註冊攔截bean的處理器,
                檢查偵聽器bean並註冊它們,例項化所有剩餘的(非延遲-init)單例。
                2、非同步開啟一個同步執行緒去時時監控容器是否被關閉,當關閉此應用程式上下文,銷燬其bean工廠中的所有bean。
                如果我們註冊了一個jvm shutdown hook,那就不需要單獨去開啟一個執行緒去監控了
            */

          
            /** 重新整理容器之後無任何操作,裡面是空的方法*/
          
            /**
             stopwatch 的作用就是記錄啟動消耗的時間,和開始啟動的時間等資訊記錄下來
            */ 

          
            /** 啟動監聽容器中所有的事件 :
                上下文已經重新整理,應用程式已經啟動,但是沒有呼叫CommandLineRunners和applicationrunner。
            */

           


            /**
            啟動監聽:
            當應用程式上下文被重新整理,所有CommandLineRunners和applicationrunner都被呼叫時,在執行方法完成之前立即呼叫。
            */

          

............................................

 

上面的原始碼中多處都是內部是用了下面原始碼中的幾個方法,其實本質就是解析載入spring-boot-2.0.3.RELEASE.jar/META-INF/spring.factories
中的配置,來建立初始化我們需要的Application context/listeners/Loaders。

private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
				SpringApplicationRunListener.class, types, this, args));
	}

	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 = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

 

spring-boot-2.0.3.RELEASE.jar/META-INF/spring.factories:
原始碼如下:


# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers\


........

 

springboot run啟動流程就是這樣!看完差不多也知道springboot啟動成功之後做了哪些!