Spring Boot 學習筆記一(SpringBoot啓動過程)


SpringBoot啓動


這裏寫圖片描述

Spring Boot通常有一個名爲*Application的入口類,在入口類裏有一個main方法,這個main方法其實就是一個標準的java應用的入口方法。
在main方法中使用SpringApplication.run方法啓動SpringBoot應用項目。


其中@SpringBootApplication是Spring Boot的核心註解,它是一個組合註解:

這裏寫圖片描述

其中@SpringBootApplication註解主要組合了@Configuration、@EnableAutoConfiguration、@ComponentScan。

如果不使用@SpringBootApplication註解,則可以使用在入口類上直接使用@Configuration、@EnableAutoConfiguration、@ComponentScan也能達到相同效果。


其中幾個註解的作用大致說一下:

@Configuration:是做類似於spring xml 工作的註解 標註在類上,類似與以前的**.xml配置文件。

@EnableAutoConfiguration:spring boot自動配置時需要的註解,會讓Spring Boot根據類路徑中的jar包依賴爲當前項目進行自動配置。同時,它也是一個組合註解。

這裏寫圖片描述

在@EnableAutoConfiguration中用了@Import註解導入EnableAutoConfigurationImportSelector類,而EnableAutoConfigurationImportSelector就是自動配置關鍵。

@Import:Spring4.2之前只支持導入配置類
        Spring4.2之後支持導入普通的java類,並將其聲明成一個bean

@ComponentScan:告訴Spring 哪個packages 的用註解標識的類 會被spring自動掃描並且裝入bean容器。

SpringBoot的自動配置:SpringBoot的一大特色就是自動配置,例如:添加了spring-boot-starter-web依賴,會自動添加Tomcat和SpringMVC的依賴,SpringBoot會對Tomcat和SpringMVC進行自動配置。
又例如:添加了spring-boot-starter-data-jpa依賴,SpringBoot會自動進行JPA相關的配置。

SpringBoot會自動掃描@SpringBootApplication所在類的同級包以及下級包的Bean(如果爲JPA項目還可以掃描標註@Entity的實體類),所以建議入口類放置在最外層包下。


spring-boot啓動過程:

這裏寫圖片描述

在這裏調用了SpringApplication的靜態run方法,並將Application類對象和main方法的參數args作爲參數傳遞了進去。


SpringApplication run方法:

這裏寫圖片描述

在這個靜態方法中,創建並構造了SpringApplication對象,並調用該對象的run方法。

構造SpringApplication對象:

這裏寫圖片描述

主要是對一些屬性附上初始值,關鍵還在與SpringApplication對象的initialize方法。

這裏寫圖片描述

SpringApplication類中的構造函數中調用initialize方法,初始化SpringApplication對象的成員變量sources,webEnvironment,initializers,listeners,mainApplicationClass。

sources: 我們傳給SpringApplication.run方法的參數。

webEnvironment:

這裏寫圖片描述

可以看到webEnvironment是一個boolean,該成員變量用來表示當前應用程序是不是一個Web應用程序。通過在classpath中查看是否存在WEB_ENVIRONMENT_CLASSES這個數組中所包含的類,如果存在那麼當前程序即是一個Web應用程序,反之則不是。

initializers:

    這裏關鍵是調用SpringApplication對象中的getSpringFactoriesInstances方法,來獲取ApplicationContextInitializer類型對象的列表。

這裏寫圖片描述

在該方法中,首先通過調用SpringFactoriesLoader.loadFactoryNames(type, classLoader)來獲取所有Spring Factories的名字(在包spring-boot-版本.jar下)。

這裏寫圖片描述

這裏寫圖片描述

可以看到,是從一個名字叫spring.factories的資源文件中,讀取key爲org.springframework.context.ApplicationContextInitializer的value。而spring.factories的部分內容如下:

這裏寫圖片描述

這裏寫圖片描述

我們從上面的spring.factories的資源文件中可以看到,得到的是     ConfigurationWarningsApplicationContextInitializer
ContextIdApplicationContextInitializer
DelegatingApplicationContextInitializer
ServerPortInfoApplicationContextInitializer這四個類的名字。

然後調用createSpringFactoriesInstances方法根據讀取到的名字創建ApplicationContextInitializer實例(框起來的兩行代碼執行創建ApplicationContextInitializer實例)。

這裏寫圖片描述

最後會將創建好的對象列表排序並返回。

SpringApplication對象的成員變量initalizers就被初始化爲,ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer這四個類的對象組成的list(這是默認的成員變量initalizers初始化)。

下面是幾個類的作用,這裏不做詳細介紹,以後會有詳解介紹:

這裏寫圖片描述

listeners:

listeners成員變量,是一個ApplicationListener<?>類型對象的集合。可以看到獲取該成員變量內容使用的是跟成員變量initializers一樣的方法,只不過傳入的類型從ApplicationContextInitializer.class變成了ApplicationListener.class。


所以重點看spring.factories中取key爲org.springframework.context.ApplicationListener的value有那些類:

這裏寫圖片描述

也就是說,listener最終會被初始化爲
ClearCachesApplicationListener
ParentContextCloserApplicationListener  FileEncodingApplicationListener
AnsiOutputApplicationListener           ConfigFileApplicationListener
DelegatingApplicationListener           LiquibaseServiceLocatorApplicationListener
ClasspathLoggingApplicationListener     LoggingApplicationListener
這幾個類的對象組成的list。

何時觸發,這裏不做詳解,等事件出現時,做詳細說明(這裏有一張網上的圖,可以瞭解):

這裏寫圖片描述

mainApplicationClass:

在deduceMainApplicationClass方法中,通過獲取當前調用棧,找到入口方法main所在的類,並將其複製給SpringApplication對象的成員變量mainApplicationClass。在我們的例子中mainApplicationClass即是我們自己編寫的Application類。

這裏寫圖片描述

現在已經構造完SpringApplication對象了,之後就是調用該對象的run方法來運行spring boot項目。

run方法:
這裏寫圖片描述

StopWatch是來自org.springframework.util的工具類,可以用來方便的記錄程序的運行時間。

這裏寫圖片描述

設置系統屬性java.awt.headless,在我們的例子中該屬性會被設置爲true,因爲我們開發的是服務器程序,一般運行在沒有顯示器和鍵盤的環境,但是還是需要相關一些數據,這樣我們就可以這樣設置系統屬性爲headless模式。

這裏寫圖片描述

run方法中,加載了一系列SpringApplicationRunListener對象,在創建和更新ApplicationContext方法前後分別調用了listeners對象的started方法和finished方法, 並在創建和刷新ApplicationContext時,將listeners作爲參數傳遞到了createAndRefreshContext方法中,以便在創建和刷新ApplicationContext的不同階段,調用listeners的相應方法以執行操作。所以,所謂的SpringApplicationRunListeners實際上就是在SpringApplication對象的run方法執行的不同階段,去執行一些操作,並且這些操作是可配置的.

創建ApplicationContext時調用startint方法

這裏寫圖片描述

更新ApplicationContext方法時調用finished方法:

這裏寫圖片描述

加載SpringApplicationRunListener,方式和initializers,listeners相同,所以我們可以在spring.factories中取key爲org.springframework.boot.SpringApplicationRunListener的value可以看出加載了哪些類:

這裏寫圖片描述

可以看出加載的是org.springframework.boot.context.event.EventPublishingRunListener類

這裏寫圖片描述

EventPublishingRunListener:

這裏寫圖片描述

EventPublishingRunListener對象在初始化時候將SpringApplication對象的成員變量listeners全都保存下來。

等到自己的public方法被調用時,發佈相應的事件,或執行相應的操作。

RunListener是在SpringApplication對象的run方法執行到不同的階段時,發佈相應的event給SpringApplication對象的成員變量listeners中記錄的事件監聽器。


SpringApplicationRunListeners相關的類結構:

這裏寫圖片描述

默認情況下,調用listeners的started方法,發佈了ApplicationStartedEvent時,我們已經加載的事件監聽器都做了什麼操作,(實現了ApplicationListener接口的類,並且事件類型是ApplicationStartingEvent類型)。

這裏寫圖片描述

這裏寫圖片描述

在默認情況下,classpath中不存在liquibase,所以不執行任何操作。

LoggingApplicationListener監聽ApplicationStartedEvent,會根據classpath中的類情況創建相應的日誌系統對象,並執行一些初始化之前的操作;

這裏寫圖片描述

默認情況下,創建的是org.springframework.boot.logging.logback.LogbackLoggingSystem類的對象,Logback是SpringBoot默認採用的日誌系統。

這裏寫圖片描述

到這裏,ApplicationStartedEvent事件的處理這樣就結束了。

創建並刷新ApplicationContext:

這裏寫圖片描述

DefaultApplicationArguments:

這裏寫圖片描述

這裏寫圖片描述

這裏是把main函數的args參數當做一個PropertySource來解析,默認情況下,args的長度是0,所以這裏創建的DefaultApplicationArguments也沒有實際的內容。

ConfigurableEnvironment:創建並配置ApplicationConext的Environment

這裏寫圖片描述

首先要調用getOrCreateEnvironment方法獲取一個Environment對象。

這裏寫圖片描述

在默認情況下,執行到此處時,environment成員變量爲null,而webEnvironment成員變量的值爲true,所以會創建一個StandardServletEnvironment對象並返回。

之後會調用ConfigurableEnvironment類型的對象的configureEnvironment方法來配置上一步獲取到的Environment對象。

這裏寫圖片描述

configureEnvironment方法先是調用configurePropertySources來配置properties,然後調用configureProfiles來配置profiles。

這裏寫圖片描述

這裏,configurePropertySources首先查看SpringApplication對象的成員變量defaultProperties,如果該變量非null且內容非空,則將其加入到Environment的PropertySource列表的最後。

之後查看SpringApplication對象的成員變量addCommandLineProperties和main函數的參數args,如果設置了addCommandLineProperties=true,且args個數大於0,那麼就構造一個由main函數的參數組成的PropertySource放到Environment的PropertySource列表的最前面(這就能保證,我們通過main函數的參數來做的配置是最優先的,可以覆蓋其他配置)。

在默認情況下,由於沒有配置defaultProperties且main函數的參數args個數爲0,所以這個函數什麼也不做。

調用configureProfiles來配置profiles:

這裏寫圖片描述

configureProfiles首先會讀取Properties中key爲spring.profiles.active的配置項,配置到Environment。

這裏寫圖片描述

再將SpringApplication對象的成員變量additionalProfiles加入到Environment的active profiles配置中。

默認情況下,配置文件裏沒有spring.profiles.active的配置項,而SpringApplication對象的成員變量additionalProfiles也是一個空的集合,所以這個函數沒有配置任何active profile。

調用SpringApplicationRunListeners類的對象listeners發佈ApplicationEnvironmentPreparedEvent事件:

到這一步時,Environment就算是配置完成了。接下來調用SpringApplicationRunListeners類的對象listeners發佈ApplicationEnvironmentPreparedEvent事件。

這裏寫圖片描述

這裏寫圖片描述

等到發佈完事件之後,我們就可以看看,加載的ApplicationListener對象都有哪些響應了這個事件,做了什麼操作:

FileEncodingApplicationListener響應該事件:

這裏寫圖片描述

檢查file.encoding配置是否與spring.mandatory_file_encoding一致
在默認情況下,因爲沒有spring.mandatory_file_encoding的配置,所以這個響應方法什麼都不做。


AnsiOutputApplicationListener響應該事件:

這裏寫圖片描述

根據spring.output.ansi.enabled和spring.output.ansi.console-available對AnsiOutput類做相應配置。在默認情況下,因爲沒有做配置,所以這個響應方法什麼都不做。


ConfigFileApplicationListener響應該事件:

這裏寫圖片描述

這裏寫圖片描述

可以看到,ConfigFileApplicationListener從META-INF/spring.factories文件中讀取EnvironmentPostProcessor配置,加載相應的EnvironmentPostProcessor類的對象,並調用其postProcessEnvironment方法。在我們的例子中,會加載CloudFoundryVcapEnvironmentPostProcessor和SpringApplicationJsonEnvironmentPostProcessor並執行,由於我們的例子中沒有CloudFoundry和Json的配置,所以這個響應,不會加載任何的配置文件到Environment中來。

這裏寫圖片描述

這裏寫圖片描述

DelegatingApplicationListener響應該事件:將配置文件中key爲context.listener.classes的配置項,加載在成員變量multicaster中

這裏寫圖片描述

這裏寫圖片描述

將配置文件中key爲context.listener.classes的配置項,加載在成員變量multicaster中,因爲在默認情況下沒有key爲context.listener.classes的Property,所以不會加載任何listener到該監聽器中。

LoggingApplicationListener響應事件:對在ApplicationStarted時加載的LoggingSystem做一些初始化工作

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

默認情況下,是對加載的LogbackLoggingSystem做一些初始化工作。

打印Banner

這裏寫圖片描述

這裏寫圖片描述

printBanner方法中,首先會調用selectBanner方法得到一個banner對象,然後判斷bannerMode的類型,如果是Banner.Mode.LOG,那麼將banner對象轉換爲字符串,打印一條info日誌,否則的話,調用banner對象的printbanner方法,將banner打印到標準輸出System.out。

默認情況下,bannerMode是Banner.Mode.Console,而且也不曾提供過banner.txt這樣的資源文件。所以selectBanner方法中得到到便是默認的banner對象,即SpringBootBanner類的對象:

這裏寫圖片描述

創建ApplicationContext:

這裏寫圖片描述

SpringApplication中調用createApplicationContext獲取創建ApplicationContext(IOC容器),可以看到,當檢測到本次程序是一個web應用程序(成員變量webEnvironment爲true)的時候,就加載類
DEFAULT_WEB_CONTEXT_CLASS,否則的話加載DEFAULT_CONTEXT_CLASS。我們的例子是一個web應用程序,所以會加載DEFAULT_WEB_CONTEXT_CLASS,也就是org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext。
AnnotationConfigEmbeddedWebApplicationContext類的作用(功能):

這裏寫圖片描述

可以看到我們加載的這個AnnotationConfigEmbeddedWebApplicationContext類,從名字就可以看出來,首先是一個WebApplicationContext實現了WebApplicationContext接口,然後是一個EmbeddedWebApplicationContext,這意味着它會自動創建並初始化一個EmbeddedServletContainer,同時還支持AnnotationConfig,會將使用註解標註的bean註冊到ApplicationContext中。

總結起來就是:指定了容器的類名,最後通過Spring的工具類初始化容器類bean (BeanUtils.instantiate(contextClass))

這裏寫圖片描述

通過調用Class對象的newInstance()方法來實例化對象,這等同於直接調用類的空的構造方法,所以我們來看AnnotationConfigEmbeddedWebApplicationContext類的構造方法:

這裏寫圖片描述

構造方法中初始化了兩個成員變量,類型分別爲AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner用以加載使用註解的bean定義。
這樣ApplicationContext對象就創建出來了,在createAndRefreshContext方法中創建了ApplicationContext對象之後會緊接着調用其setEnvironment將我們之前準備好的Environment對象賦值進去。之後分別調用postProcessApplicationContext和applyInitializers做一些處理和初始化的操作。

這裏寫圖片描述

如果成員變量beanNameGenerator不爲Null,那麼爲ApplicationContext對象註冊beanNameGenerator bean。如果成員變量resourceLoader不爲null,則爲ApplicationContext對象設置ResourceLoader。默認情況下,這兩個成員變量都爲Null,所以什麼都不做。

這裏寫圖片描述

這裏寫圖片描述

最後刷新上下文:

這裏寫圖片描述

這裏調用的是AbstractApplicationContext的refresh()方法刷新上下文。
1、this.prepareRefresh();
準備啓動spring容器,設置容器的啓動日期和活動標誌

2、ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
主要是創建beanFactory,同時加載配置文件.xml中的beanDefinition  
通過String[] configLocations = getConfigLocations()獲取資源路徑,然後加載beanDefinition  

3、this.prepareBeanFactory(beanFactory);
給beanFactory註冊一些標準組件,如ClassLoader,StandardEnvironment,BeanProcess  

4、this.postProcessBeanFactory(beanFactory);
提供給子類實現一些postProcess的註冊,如AbstractRefreshableWebApplicationContext註冊一些Servlet相關的postProcess,真對web進行生命週期管理的Scope,通過registerResolvableDependency()方法註冊指定ServletRequest,HttpSession,WebRequest對象的工廠方法。

5、this.invokeBeanFactoryPostProcessors(beanFactory);
調用所有BeanFactoryProcessor的postProcessBeanFactory()方法  

6、this.registerBeanPostProcessors(beanFactory);
註冊BeanPostProcessor,BeanPostProcessor作用是用於攔截Bean的創建  

7、this.initMessageSource();
初始化消息Bean  

8、this.initApplicationEventMulticaster();
初始化上下文的事件多播組建,ApplicationEvent觸發時由multicaster通知給ApplicationListener  

9、this.onRefresh();
ApplicationContext初始化一些特殊的bean 

10、this.registerListeners();
註冊事件監聽器,事件監聽Bean統一註冊到multicaster裏頭,ApplicationEvent事件觸發後會由multicaster廣播  

11、this.finishBeanFactoryInitialization(beanFactory);
非延遲加載的單例Bean實例化

12、this.finishRefresh();
結束刷新

結束刷新容器之後執行的afterRefresh()方法:

這裏寫圖片描述

這裏寫圖片描述

實際應用中,我們會有在項目服務啓動的時候就去加載一些數據或做一些事情這樣的需求。比如讀取配置文件,數據庫連接之類的。SpringBoot給我們提供了兩個接口來幫助我們實現這種需求。這兩個接口分別爲CommandLineRunner和ApplicationRunner。他們的執行時機爲容器啓動完成的時候。
這兩個接口中有一個run方法,我們只需要實現這個方法即可。這兩個接口的不同之處在於:ApplicationRunner中run方法的參數爲ApplicationArguments,而CommandLineRunner接口中run方法的參數爲String數組。

結束監聽器:

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

從上述代碼中可以看出,finished()方法是通過發佈**結束監聽**這個事件,當實現onApplication(相應事件)的監聽類監聽到這個事件時,就會結束相應的監聽。

結束計時:

stopWatch.stop();

開始打印Log信息:

這裏寫圖片描述

找到你的入口函數(xxApplication.class),開始打印Log信息。

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