SpringApplication:SpringBoot程序啟動的一站式解決方案

我們說SpringBoot是Spring框架對“約定優先于配置(Convention Over Configuration)"理念的最佳實踐的產物,一個典型的SpringBoot應用本質上其 實就是一個基于Spring框架的應用

如果非說SpringBoot微框架提供了點兒自己特有的東西,在核心類層面(除了各種場景下的自動配置一站式插拔模塊),也就是SpringApplication了。

SpringApplication將一個典型的Spring應用啟動的流程“模板化”(這里是動詞),在沒有特殊需求的情況下,默認模板化后的執行流程就可以滿足需求了;但有特殊需求也沒有關系,SpringApplication在合適的流程結點開放了一系列不同類型的擴展點,我們可以通過這些擴展點對SpringBoot程序的啟動和關閉過程進行擴展。

最簡單的擴展或配置是SpringApplication通過一系列設置方法(setters)開發的定制方式 . . .

大部分情況下,SpringApplication已經提供了很好的默認設置,所以,我們不再對這些表層進行探究了,因為對表層之下的東西進行探究才是我們的最終目的。

一、SpringApplication執行流程

SpringApplication的(靜態)run方法的實現的主要流程大體可以歸納如下:

  1. SpringApplication實例初始化

    SpringApplication實例初始化時,它會提前做幾件事情:

    • 根據classpath里面是否存在某個特征類(org.springframework.web.context.ConfigurableWebApplicationContext)來決定是否應該創建一個為Web應用使用的ApplicationContext類型,還是應該創建一個標準Standalone應用使用的ApplicationContext類型。
    • 使用SpringFactoriesLoader在應用的classpath中查找并加載所有可用的ApplicationContextInitializer。
    • 使用SpringFactoriesLoader在應用的classpath中查找并加載所有可用的ApplicationListener。
    • 推斷并設置main方法的定義類。
  2. SpringApplication實例初始化完成并且完成設置后,就開始執行run方法的邏輯了

    方法執行伊始,首先遍歷執行所有通過SpringFactoriesLoader可以查找到并加載的SpringApplicationRunListener,調用它們的started()方法,告訴這些SpringApplicationRunListener,SpringBoot應用要開始執行了

  3. 創建并配置當前SpringBoot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile)。

  4. 遍歷調用所有SpringApplicationRunListener的environmentPrepared()的方法,告訴它們:當前SpringBoot應用使用的Environment準備了

  5. 如果SpringApplication的showBanner屬性被設置為true,則打印Banner.Mode

  6. 根據用戶是否明確設置了applicationContextClass類型以及初始化階段的推斷結果,決定該為當前SpringBoot應用創建什么類型的ApplicationContext并創建完成,然后根據條件決定是否添加ShutdownHook,決定是否使用自定義的BeanNameGenerator,決定是否使用自定義的ResourceLoader,當然,最重要的,將之前準備好的Environment設置給創建好的ApplicationContext使用。

  7. ApplicationContext創建好之后,SpringApplication會再次借助Spring-FactoriesLoader,查找并加載classpath中所有可用的ApplicationContext-Initializer,然后遍歷調用這些ApplicationContextInitializer的initialize (applicationContext)方法來對已經創建好的ApplicationContext進行進一步的處理。

  8. 遍歷調用所有SpringApplicationRunListener的contextPrepared()方法, 通知它們:“SpringBoot應用使用的ApplicationContext準備好啦!”

  9. 最核心的一步,將之前通過@EnableAutoConfiguration獲取的所有配置以及其他形式的IoC容器配置加載到已經準備完畢的ApplicationContext。

  10. 遍歷調用所有SpringApplicationRunListener的contextLoaded()方法,告知所有SpringApplicationRunListener,ApplicationContext”裝填完畢”!

  11. 調用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。

  12. 查找當前ApplicationContext中是否注冊有CommandLineRunner,如果有,則遍歷執行它們。

  13. 正常情況下,遍歷執行SpringApplicationRunListener的finished()方法,告知它們:“搞定!”。

    (如果整個過程出現異常,則依然調用所有SpringApplicationRunListener的finished()方法,只不過這種情況下會將異常信息一并傳入處理)。

整個過程看起來冗長無比,但其實很多都是一些事件通知的擴展點,如果我們將這些邏輯暫時忽略,那么,其實整個SpringBoot應用啟動的邏輯就可以壓縮到極其精簡的幾步。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UstTIBSw-1614751600844)(https://i.loli.net/2018/01/21/5a646ee77ff52.jpg)]

前后對比我們就可以發現,其實SpringApplication提供的這些各類擴展點 近乎“喧賓奪主”,占據了一個Spring應用啟動邏輯的大部分“江山”,除了初 始化并準備好ApplicationContext,剩下的大部分工作都是通過這些擴展點完成 的,所以,我們有必要對各類擴展點進行逐一剖析,以便在需要的時候可以信 手拈來,為我所用。

二、SpringApplicationRunListener

SpringApplicationRunListener是一個只有SpringBoot應用的main方法執行過程中接收不同執行時點事件通知的監聽者:

public interface SpringApplicationRunListener { void started(); void environmentPrepared( ConfigurableEnvironment environment); void contextPrepared( ConfigurableApplicationContext context); void contextLoaded( ConfigurableApplicationContext context); void finished( ConfigurableApplicationContext context, Throwable exception); 
}

對于我們來說,基本沒什么常見的場景需要自己實現一個SpringApplicationRunListener,即使SpringBoot默認也只實現了一個org.springframework.boot.context.event.EventPublishingRunListener,用于在SpringBoot啟動的不同時點發布不同的應用事件類型(ApplicationEvent),如果有哪些ApplicationListener對這些應用事件感興趣,則可以接收并處理。

假設我們真的有場景需要自定義一個SpringApplicationRunListener實現,那么有一點需要注意,即任何一個SpringApplicationRunListener實現類的構造方法(Constructor)需要有兩個構造參數,一個構造參數的類型就是我們的org.springframework.boot.SpringApplication,另外一個就是args參數列表的String[]:

public class DemoSpringApplicationRunListener implements SpringApplicationRunListener { @Override public void started() {// do whatever you want to do } @Override public void environmentPrepared( ConfigurableEnvironment environment) { // do whatever you want to do } @Override public void contextPrepared( ConfigurableApplicationContext context) { // do whatever you want to do } @Override public void contextLoaded( ConfigurableApplicationContext context) { // do whatever you want to do } @Override public void finished( ConfigurableApplicationContext context, Throwable exception) { // do whatever you want to do } 

之后,我們可以通過SpringFactoriesLoader立下的規矩,在當前SpringBoot應用的classpath下的META-INF/spring.factories文件中進行類似如下的配置:

org.springframework.boot.SpringApplicationRunListener=\
com.self.springboot.demo.DemoSpringApplicationRunListener

然后SpringApplication就會在運行的時候調用它啦!

三、ApplicationListener

ApplicationListener其實是老面孔,屬于Spring框架對Java中實現的監聽者模式的一種框架實現,這里唯一值得著重強調的是,對于初次接觸SpringBoot,但對Spring框架本身又沒有過多接觸的開發者來說,可能會將這個名字與SpringApplicationRunListener混淆。

如果我們要為SpringBoot應用添加自定義的ApplicationListener,有兩種方式:

通過SpringApplication.addListeners(… )或者SpringApplication.setListeners(… )方法添加一個或者多個自定義的ApplicationListener;

借助SpringFactoriesLoader機制,在META-INF/spring.factories文件中添加配置(以下代碼是為SpringBoot默認注冊的ApplicationListener配置)

org.springframework.context.ApplicationListener=
org.springframework.boot.builder.ParentContextCloserApplicationListener,
org.springframework.boot.cloudfoundry.VcapApplicationListener,
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.liquibase.LiquibaseServiceLocatorApplicat- ionListener,
org.springframework.boot.logging.ClasspathLoggingApplicationListener,
org.springframework.boot.logging.LoggingApplicationListener

關于ApplicationListener,我們就說這些。

四、ApplicationContextInitializer

ApplicationContextInitializer也是Spring框架原有的概念,這個類的主要目的是在ConfigurableApplictaionContext類型(或者子類型)的ApplicationContext的refresh之前,允許我們對ConfigurableApplicationContext的實例做進一步的設置和處理。

實現一個ApplicationContextInitializer很簡單,因為它只有一個方法需要實現:

public class DemoApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize( ConfigurableApplicationContext applicationContext) { // do whatever you want with applicationContext, // e. g. applicationContext. registerShutdownHook();}
}

不過,一般情況下我們基本不會需要自定義一個ApplicationContextInitializer,即使SpringBoot框架默認也只是注冊了三個實現:

不過,一般情況下我們基本不會需要自定義一個ApplicationContextInitializer,即使SpringBoot框架默認也只是注冊了三個實現:

org. springframework. context. ApplicationContextInitializer=\
org. springframework. boot. context. ConfigurationWarningsApplication- ContextInitializer,\
org. springframework. boot. context. ContextIdApplicationContextInitia- lizer,\
org. springframework. boot. context. config. DelegatingApplicationContex- tInitializer

如果我們真的需要自定義一個ApplicationContextInitializer,那么只要像上面這樣,通過SpringFactoriesLoader機制進行配置,或者通過SpringApplication.addInitializers(…) 設置即可。

五、CommandLineRunner

CommandLineRunner屬于SpringBoot應用特定的回調擴展接口:

public interface CommandLineRunner { void run( String... args) throws Exception; 
}

CommandLineRunner需要關注的兩點:

  1. 所有CommandLineRunner的執行時點在SpringBoot應用的ApplicationContext完全初始化開始工作之后(可以認為是main方法執行完成之前最后一步)

  2. 只要存在于當前SpringBoot應用的ApplicationContext中的任何CommandLineRunner,都會被加載執行(不管你是手動注冊這個CommandLineRunner到IoC容器,還是自動掃描進去的)

與其他幾個擴展點接口類型相似,建議CommandLineRunner的實現類使用@org.springframework.core.annotation.Order進行標注或者實現org.springframework.core.Ordered接口,便于對它們的執行順序進行調整,這其實十分重要,我們不希望順序不當的CommandLineRunner實現類阻塞了后面其他CommandLineRunner的執行。

參考:

  • 《SpringBoot揭秘+快速構建微服務體系》 第三章

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/535539.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/535539.shtml
英文地址,請注明出處:http://en.pswp.cn/news/535539.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

了解spring-boot-starter

SpringBoot提供了針對日常企業應用研發各種場景的spring-boot-starter自動配置 依賴模塊,如此多“開箱即用”的依賴模塊,使得開發各種場景的Spring應用 更加快速和高效,本文會就幾個常見的通用spring-boot-starter模塊進行了解 一、約定優先于…

常用腳本

mysql狀態收集 #! /bin/bash#mysql for zabbixUptime() {mysqladmin status I awk [print $2]}Slow_ queries() {mysqladmin status | awk [print $9]} ICom_ insert() {mysqladmin extended-status |awk /<Com_ insertl>/[print $4]}Com_ delete() {mysqladmi…

linux產生隨機數方法

如果產生的數據長短格式不統一&#xff0c;使用md5sum命令&#xff0c;并使用cut截取相應位數echo $RANDOM openssl rand -base64 openssl rand -base64 10 date %s%N /dev/random設備&#xff0c;存儲著系統當前運行的環境的實時數據。它可以看作是系統某個時候&#x…

oracle視圖等

視圖 視圖是基于其他表或視圖創建的邏輯表 視圖不包含自己的數據&#xff0c;它基于的表稱為基表 使用視圖是為了: 限制對數據的訪問 使復雜的查詢簡單化 提供數據的獨立性 相同的數據展現不同的視圖 不能刪除行不能修改行不能添加行 --分組函數 - -GRoUP BY子句 -…

Java集合:關于 ArrayList 的內容盤點

本篇內容包括&#xff1a;ArrayList 概述、ArrayList 的擴容機制&#xff08;包含源碼部分&#xff09;、如何在遍歷 ArrayList 時正確的移除一個元素、ArrayList 的構造方法及常用方法、關于 Array 與 ArrayList 的區別、關于 CopyOnWriteArrayList、關于 Fail Fast 與 Fail S…

Java集合:關于 LinkedList 的內容盤點

本篇內容包括&#xff1a;LinkedList 的概述、LinkedList 的結構既雙向鏈表實現與LinkedList-Node 結構、LinkedList 的使用&#xff08;構造方法&常用方法&#xff09;、關于 Queue 隊列的介紹、關于 ArrayList 和 LinkedList 的區別以及算法&#xff1a;翻轉鏈表&#xf…

shell自動化巡檢

#!/bin/bash #主機信息每日巡檢IPADDR$(ifconfig eth0|grep inet addr|awk -F [ :] {print $13}) #環境變量PATH沒設好&#xff0c;在cron里執行時有很多命令會找不到 export PATH/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin source /etc/profile…

Java集合:關于 Vector 的內容盤點

Vector 與 ArrayList 一樣&#xff0c;也是通過數組實現的&#xff0c;不同的是它支持線程的同步&#xff0c;即某一時刻只有一個線程能夠寫 Vector&#xff0c;避免多線程同時寫而引起的不一致性&#xff0c;但實現同步需要很高的花費&#xff0c;因此&#xff0c;訪問它比訪問…

memcached 的基本命令

memcached 的基本命令(安裝、卸載、啟動、配置相關)&#xff1a; -p 監聽的端口 -l 連接的 IP 地址, 默認是本機 -d start 啟動 memcached 服務 -d restart 重起 memcached 服務 -d stop|shutdown 關閉正在運行的 memcached 服務 -d install 安裝 memcached 服務 -d uninstall …

Java集合:關于 HashSet 的內容盤點

哈希表存放的是哈希值&#xff0c; HashSet 存儲元素的順序并不是按照存入時的順序&#xff08;和 List 顯然不同&#xff09; 而是按照哈希值來存的所以取數據也是按照哈希值取得。 &#xff5e; 本篇內容包括&#xff1a;HashSet 概述、HashSet 與 HashMap 的關系以及HashSet…

mysql備份腳本

#!/bin/bash #保留備份個數&#xff0c;會刪除時間較早的.dump備份 number3 #設置備份保存路徑&#xff0c;yourpath替換成自己的備份保存路徑 backup_diryourpath #日期格式 dddate %Y%m%d #備份工具 toolmysqldump #數據庫用戶名 usernameroot #數據庫密碼&#xff0c;由于密…

Java集合:關于 TreeSet 的內容盤點

TreeSet() 是使用二叉樹的原理對新 add() 的對象按照指定的順序排序&#xff08;升序、降序&#xff09;&#xff0c;每增加一個對象都會進行排序&#xff0c;將對象插入的二叉樹指定的位置&#xff1b; ~ 本篇內容包括&#xff1a;TreeSet 概述、TreeSet 的使用以及其他知識點…

python求素數

口求100內的素數 -個數能被從2開始到自己的平發根的正整數整數整除,就是合數 import math n100 for X in range(2, n): for i in range(2, math.ceil(math.sqrt(x))): if x %i 0: break else: print(x)口求100內的素數 合數一定可以分解為幾個質數的乘積 import math n100 pri…

svn鉤子腳本

REP0S"$1" REV"$2"export LANGen_US.UTF-8 LOGPATH"/app/log" [ !-d ${LOGPATH}] && mkdir $[LOGPATH) -p #update content from svn↓14 SVN/usr/bin/svn↓ SVN update --username test --password test /data/ if[ $? -eq ] then /us…

shell判斷字符串是否為數字

#1.組合語法判斷1: [ -n "echo $num|sed s/[0-9]//g" -a -n "echo $2|sed s/[0-9]//g"] &&\echo”兩個參數都必須為數字”&& exit 1#2.組合語法判斷2:[ -n "echo $num|sed s/[0-9]//g" -a -n "echo $2|sed s/[0-9]//g&…

MySQL:DQL 數據查詢語句盤點

本篇內容包括&#xff1a;DQL 的簡介、SELECT 語句、WHERE 條件語句、JOIN 連接查詢(多表查詢)和分組、過濾、排序、分頁、子查詢的使用。 一、DQL 簡介 DQL&#xff08;Data QueryLanguage&#xff09;語句&#xff0c;即數據查詢語句 常用的語句關鍵字有&#xff1a;SELECT…

MySQL:DML 數據操作語句盤點

本篇內容包括&#xff1a;DML 的簡介、INSERT 命令、UPDATE 命令、DELETE 命令以及 TRUNCATE 命令的使用。 一、DML 簡介 DML&#xff08;Data Manipulation Language&#xff09;語句&#xff0c;即數據操作語句&#xff0c;用于操作數據庫對象中所包含的數據。 常用關鍵字包…

MySQL:DDL 數據定義語句盤點

本篇內容包括&#xff1a;DDL 的簡介、SHOW 查看語句、CREATE 創建語句、ALTER 修改語句以及 DROP 刪除語句的使用。 一、DDL 簡介 DDL&#xff08;Data Definition Language&#xff09;&#xff0c;即數據定義語句&#xff0c;功能就是定義數據庫DATabase、表table、索引ind…

MySQL:DCL 數據控制語句盤點

本篇內容包括:DCL 簡介、GRANT、REVOKE、COMMIT、ROLLBACK、SAVEPOINT、LOCK命令的使用。 一、DCL 簡介 DCL&#xff08;Data Control Language&#xff09;語句&#xff0c;即數據控制語句&#xff0c;用于設置或更改數據庫用戶或角色權限的語句 常用關鍵字包括&#xff1a;…

oracle遷移父子數據

現有需求如下&#xff0c;業務組織單元表中id字段數據在另外一個系統全部重復&#xff0c;但需要將此業務單元組織導入另一系統 業務組織單元表Isc_Specialorg_Unit 表中存在ID字段為子節點數據&#xff0c;parent_id為父節點數據&#xff0c;orgpath為組織路徑 現在做如下操…