當前位置:首頁 » 操作系統 » springboot源碼

springboot源碼

發布時間: 2023-12-11 06:21:42

1. SpringBoot內置生命周期事件詳解 SpringBoot源碼(十)

SpringBoot中文注釋項目Github地址:

https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE

本篇接 SpringBoot事件監聽機制源碼分析(上) SpringBoot源碼(九)

溫故而知新,我們來簡單回顧一下上篇的內容,上一篇我們分析了 SpringBoot啟動時廣播生命周期事件的原理 ,現將關鍵步驟再濃縮總結下:

上篇文章的側重點是分析了SpringBoot啟動時廣播生命周期事件的原理,此篇文章我們再來詳細分析SpringBoot內置的7種生命周期事件的源碼。

分析SpringBoot的生命周期事件,我們先來看一張類結構圖:

由上圖可以看到事件類之間的關系:

EventObject 類是JDK的事件基類,可以說是所有java事件類的基本,即所有的Java事件類都直接或間接繼承於該類,源碼如下:

可以看到 EventObject 類只有一個屬性 source ,這個屬性是用來記錄最初事件是發生在哪個類,舉個栗子,比如在SpringBoot啟動過程中會發射 ApplicationStartingEvent 事件,而這個事件最初是在 SpringApplication 類中發射的,因此 source 就是 SpringApplication 對象。

ApplicationEvent 繼承了DK的事件基類 EventObject 類,是Spring的事件基類,被所有Spring的具體事件類繼承,源碼如下:

可以看到 ApplicationEvent 有且僅有一個屬性 timestamp ,該屬性是用來記錄事件發生的時間。

SpringApplicationEvent 類繼承了Spring的事件基類 ApplicationEvent ,是所有SpringBoot內置生命周期事件的父類,源碼如下:

可以看到 SpringApplicationEvent 有且僅有一個屬性 args ,該屬性就是SpringBoot啟動時的命令行參數即標注 @SpringBootApplication 啟動類中 main 函數的參數。

接下來我們再來看一下 SpringBoot 內置生命周期事件即 SpringApplicationEvent 的具體子類們。

SpringBoot開始啟動時便會發布 ApplicationStartingEvent 事件,其發布時機在環境變數Environment或容器ApplicationContext創建前但在注冊 ApplicationListener 具體監聽器之後,標志標志 SpringApplication 開始啟動。

可以看到 事件多了一個 environment 屬性,我們不妨想一下,多了 environment 屬性的作用是啥?
答案就是 事件的 environment 屬性作用是利用事件發布訂閱機制,相應監聽器們可以從 事件中取出 environment 變數,然後我們可以為 environment 屬性增加屬性值或讀出 environment 變數中的值。

當SpringApplication已經開始啟動且環境變數 Environment 已經創建後,並且為環境變數 Environment 配置了命令行和 Servlet 等類型的環境變數後,此時會發布 事件。

監聽 事件的第一個監聽器是 ConfigFileApplicationListener ,因為是 ConfigFileApplicationListener 監聽器還要為環境變數 Environment 增加 application.properties 配置文件中的環境變數;此後還有一些也是監聽 事件的其他監聽器監聽到此事件時,此時可以說環境變數 Environment 幾乎已經完全准備好了。

可以看到 事件多了個 類型的 context 屬性, context 屬性的作用同樣是為了相應監聽器可以拿到這個 context 屬性執行一些邏輯,具體作用將在 3.4.4 詳述。

事件在 ApplicationContext 容器創建後,且為 ApplicationContext 容器設置了 environment 變數和執行了 的初始化方法後但在bean定義載入前觸發,標志ApplicationContext已經初始化完畢。

同樣可以看到 ApplicationPreparedEvent 事件多了個 類型的 context 屬性,多了 context 屬性的作用是能讓監聽該事件的監聽器們能拿到 context 屬性,監聽器拿到 context 屬性一般有如下作用:

ApplicationPreparedEvent 事件在 ApplicationContext 容器已經完全准備好時但在容器刷新前觸發,在這個階段 bean 定義已經載入完畢還有 environment 已經准備好可以用了。

ApplicationStartedEvent 事件將在容器刷新後但 ApplicationRunner 和 CommandLineRunner 的 run 方法執行前觸發,標志 Spring 容器已經刷新,此時容器已經准備完畢了。

ApplicationReadyEvent 事件在調用完 ApplicationRunner 和 CommandLineRunner 的 run 方法後觸發,此時標志 SpringApplication 已經正在運行。

可以看到 ApplicationFailedEvent 事件除了多了一個 context 屬性外,還多了一個 Throwable 類型的 exception 屬性用來記錄SpringBoot啟動失敗時的異常。

ApplicationFailedEvent 事件在SpringBoot啟動失敗時觸發,標志SpringBoot啟動失敗。

此篇文章相對簡單,對SpringBoot內置的7種生命周期事件進行了詳細分析。我們還是引用上篇文章的一張圖來回顧一下這些生命周期事件及其用途:

由於有一些小夥伴們建議之前有些源碼分析文章太長,導致耐心不夠,看不下去,因此,之後的源碼分析文章如果太長的話,筆者將會考慮拆分為幾篇文章,這樣就比較短小了,比較容易看完,嘿嘿。

【源碼筆記】Github地址:

https://github.com/yuanmabiji/Java-SourceCode-Blogs

點贊搞起來,嘿嘿嘿!

公眾號【 源碼筆記 】專注於Java後端系列框架的源碼分析。

2. [Spring boot源碼解析] 2 啟動流程分析

在了解 Spring Boot 的啟動流程的時候,我們先看一下一個Spring Boot 應用是如何啟動的,如下是一個簡單的 SpringBoot 程序,非常的簡潔,他是如何做到的呢,我們接下來就將一步步分解。

我們追蹤 SpringApplication.run() 方法,其實最終它主要的邏輯是新建一個 SpringApplication ,然後調用他的 run 方法,如下:

我們先來看一下創建 SpringApplication 的方法:

在將Main class 設置 primarySources 後,調用了 WebApplicationType.deceFromClasspath() 方法,該方法是為了檢查當前的應用類型,並設置給 webApplicationType 。 我們進入 deceFromClasspath 方法 :

這里主要是通過類載入器判斷是否存在 REACTIVE 相關的類信息,假如有就代表是一個 REACTIVE 的應用,假如不是就檢查是否存在 Servelt 和 ,假如都沒有,就代表應用為非 WEB 類應用,返回 NONE ,默認返回 SERVLET 類型,我們這期以我們目前最常使用的 SERVLET 類型進行講解,所以我們在應用中引入了 spring-boot-starter-web 作為依賴:

他會包含 Spring-mvc 的依賴,所以就包含了內嵌 tomcat 中的 Servlet 和 Spring-web 中的 ,因此返回了 SERVLET 類型。

回到剛才創建 SpringApplication 的構建方法中,我們設置完成應用類型後,就尋找所有的 Initializer 實現類,並設置到 SpringApplication 的 Initializers 中,這里先說一下 getSpringFactoriesInstances 方法,我們知道在我們使用 SpringBoot 程序中,會經常在 META-INF/spring.factories 目錄下看到一些 EnableAutoConfiguration ,來出發 config 類注入到容器中,我們知道一般一個 config 類要想被 SpringBoot 掃描到需要使用 @CompnentScan 來掃描具體的路徑,對於 jar 包來說這無疑是非常不方便的,所以 SpringBoot 提供了另外一種方式來實現,就是使用 spring.factories ,比如下面這個,我們從 Springboot-test 中找到的例子,這里先定義了一個ExampleAutoConfiguration,並加上了 Configuration 註解:

然後在 spring.factories 中定義如下:

那這種方式是怎麼實現的你,這就要回到我們剛才的方法 getSpringFactoriesInstances :

我們先來看一下傳入參數,這里需要注意的是 args,這個是初始化對應 type 的時候傳入的構造參數,我們先看一下 SpringFactoriesLoader#loadFactoryNames 方法:

首先是會先檢查緩存,假如緩存中存在就直接返回,假如沒有就調用 classLoader#getResources 方法,傳入 META-INF/spring.factories ,即獲取所有 jar 包下的對應文件,並封裝成 UrlResource ,然後使用 PropertiesLoaderUtils 將這些信息讀取成一個對一對的 properties,我們觀察一下 spring.factories 都是按 properties 格式排版的,假如有多個就用逗號隔開,所以這里還需要將逗號的多個類分隔開來,並加到 result 中,由於 result 是一個 LinkedMultiValueMap 類型,支持多個值插入,最後放回緩存中。最終完成載入 META-INF/spring.factories 中的配置,如下:

我們可以看一下我們找到的 initializer 有多少個:

在獲取到所有的 Initializer 後接下來是調用 方法進行初始化。

這里的 names 就是我們上面通過類載入器載入到的類名,到這里會先通過反射生成 class 對象,然後判斷該類是否繼承與 ApplicationContextInitializer ,最後通過發射的方式獲取這個類的構造方法,並調用該構造方法,傳入已經定義好的構造參數,對於 ApplicationContextInitializer 是無參的構造方法,然後初始化實例並返回,回到原來的方法,這里會先對所有的 ApplicationContextInitializer 進行排序,調用 #sort(instances) 方法,這里就是根據 @Order 中的順序進行排序。

接下來是設置 ApplicationListener ,我們跟進去就會發現這里和上面獲取 ApplicationContextInitializer 的方法如出一轍,最終會載入到如圖的 15 個 listener (這里除了 外,其他都是 SpringBoot 內部的 Listener):

在完成 SpringApplication 對象的初始化後,我們進入了他的 run 方法,這個方法幾乎涵蓋了 SpringBoot 生命周期的所有內容,主要分為九個步驟,每一個步驟這里都使用註解進行標識:

主要步驟如下:
第一步:獲取 SpringApplicationRunListener, 然後調用他的 staring 方法啟動監聽器。
第二步:根據 SpringApplicationRunListeners以及參數來准備環境。
第三步:創建 Spring 容器。
第四步:Spring 容器的前置處理。
第五步:刷新 Spring 容器。
第六步: Spring 容器的後置處理器。
第七步:通知所有 listener 結束啟動。
第八步:調用所有 runner 的 run 方法。
第九步:通知所有 listener running 事件。
我們接下來一一講解這些內容。

我們首先看一下第一步,獲取 SpringApplicationRunListener :

這里和上面獲取 initializer 和 listener 的方式基本一致,都是通過 getSpringFactoriesInstances , 最終只找到一個類就是: org.springframework.boot.context.event.EventPublishingRunListener ,然後調用其構造方法並傳入產生 args , 和 SpringApplication 本身:

我們先看一下構造函數,首先將我們獲取到的 ApplicationListener 集合添加到initialMulticaster 中, 最後都是通過操作 來進行廣播,我,他繼承於 ,我們先看一下他的 addApplicationListener 方法:

我們可以看出,最後是放到了 applicationListenters 這個容器中。他是 defaultRetriever 的成員屬性, defaultRetriever 則是 的私有類,我們簡單看一下這個類:

我們只需要看一下這里的 getApplicationListeners 方法,它主要是到 beanFactory 中檢查是否存在多的 ApplicationListener 和舊的 applicationListeners 組合並返回,接著執行 listener 的 start 方法,最後也是調用了 的 multicastEvent 查找支持對應的 ApplicationEvent 類型的通知的 ApplicationListener 的 onApplicationEvent 方法 ,這里除了會:

篩選的方法如下,都是調用了對應類型的 supportsEventType 方法 :

如圖,我們可以看到對 org.springframework.boot.context.event.ApplicationStartingEvent 感興趣的有5個 Listener

環境准備的具體方法如下:

首先是調用 getOrCreateEnvironment 方法來創建 environment ,我們跟進去可以發現這里是根據我們上面設置的環境的類型來進行選擇的,當前環境會創建 StandardServletEnvironment

我們先來看一下 StandardServletEnvironment 的類繼承關系圖,我們可以看出他是繼承了 AbstractEnvironment :

他會調用子類的 customizePropertySources 方法實現,首先是 StandardServletEnvironment 的實現如下,他會添加 servletConfigInitParams , servletContextInitParams , jndiProperties 三種 properties,當前調試環境沒有配置 jndi properties,所以這里不會添加。接著調用父類的 customizePropertySources 方法,即調用到了 StandardEnvironment 。

我們看一下 StandardEnvironment#customizePropertySources 方法,與上面的三個 properties 創建不同,這兩個是會進行賦值的,包括系統環境變數放入 systemEnvironment 中,jvm 先關參數放到 systemProperties 中:

這里會添加 systemEnvironment 和 systemProperties 這兩個 properties,最終拿到的 properties 數量如下 4個:

在創建完成 Environment 後,接下來就到了調用 configureEnvironment 方法:

我們先看一下 configurePropertySources 方法,這里主要分兩部分,首先是查詢當前是否存在 defaultProperties ,假如不為空就會添加到 environment 的 propertySources 中,接著是處理命令行參數,將命令行參數作為一個 CompositePropertySource 或則 添加到 environment 的 propertySources 裡面,

接著調用 ConfigurationPropertySources#attach 方法,他會先去 environment 中查找 configurationProperties , 假如尋找到了,先檢查 configurationProperties 和當前 environment 是否匹配,假如不相等,就先去除,最後添加 configurationProperties 並將其 sources 屬性設置進去。

回到我們的 prepareEnvironment 邏輯,下一步是通知觀察者,發送 事件,調用的是 SpringApplicationRunListeners#environmentPrepared 方法,最終回到了 #multicastEvent 方法,我們通過 debug 找到最後對這個時間感興趣的 Listener 如下:

其主要邏輯如下:

這個方法最後載入了 PropertySourceLoader , 這里主要是兩種,一個是用於 Properties 的,一個是用於 YAML 的如下:

其中 apply 方法主要是載入 defaultProperties ,假如已經存在,就進行替換,而替換的目標 PropertySource 就是 load 這里最後的一個 consumer 函數載入出來的,這里列一下主要做的事情:
1、載入系統中設置的所有的 Profile 。
2、遍歷所有的 Profile ,假如是默認的 Profile , 就將這個 Profile 加到 environment 中。
3、調用load 方法,載入配置,我們深入看一下這個方法:

他會先調用 getSearchLocations 方法,載入所有的需要載入的路徑,最終有如下路徑:

其核心方法是遍歷所有的 propertySourceLoader ,也就是上面載入到兩種 propertySourceLoader ,最紅 loadForFileExtension 方法,載入配置文件,這里就不展開分析了,說一下主要的作用,因為每個 propertySourceLoader 都有自己可以載入的擴展名,默認擴展名有如下四個 properties, xml, yml, yaml,所以最終拿到文件名字,然後通過 - 拼接所有的真實的名字,然後加上路徑一起載入。

接下來,我們分析 BackgroundPreinitializer ,這個方法在接收 ApplicationPrepareEnvironment 事件的時候真正調用了這份方法:

1、 ConversionServiceInitializer 主要負責將包括 日期,貨幣等一些默認的轉換器注冊到 formatterRegistry 中。
2、 ValidationInitializer 創建 validation 的匹配器。
3、 MessageConverterInitializer 主要是添加了一些 http 的 Message Converter。
4、 JacksonInitializer 主要用於生成 xml 轉換器的。
接著回到我們將的主體方法, prepareEnvironment 在調用完成 listeners.environmentPrepared(environment) 方法後,調用 bindToSpringApplication(environment) 方法,將 environment 綁定到 SpirngApplication 中。
接著將 enviroment 轉化為 StandardEnvironment 對象。
最後將 configurationProperties 加入到 enviroment 中, configurationProperties 其實是將 environment 中其他的 PropertySource 重新包裝了一遍,並放到 environment 中,這里主要的作用是方便 進行解析。

它主要是檢查是否存在 spring.beaninfo.ignore 配置,這個配置的主要作用是設置 javaBean 的內省模式,所謂內省就是應用程序在 Runtime 的時候能檢查對象類型的能力,通常也可以稱作運行時類型檢查,區別於反射主要用於修改類屬性,內省主要用戶獲取類屬性。那麼我們什麼時候會使用到內省呢,java主要是通過內省工具 Introspector 來完成內省的工作,內省的結果通過一個 Beaninfo 對象返回,主要包括類的一些相關信息,而在 Spring中,主要是 BeanUtils#Properties 會使用到,Spring 對內省機制還進行了改進,有三種內省模式,如下圖中紅色框框的內容,默認情況下是使用 USE_ALL_BEANINFO。假如設置為true,就是改成第三中 IGNORE_ALL_BEANINFO

首先是檢查 Application的類型,然後獲取對應的 ApplicationContext 類,我們這里是獲取到了 org.springframework.boot.web.servlet.context. 接著調用 BeanUtils.instantiateClass(contextClass); 方法進行對象的初始化。

最終其實是調用了 的默認構造方法。我們看一下這個方法做了什麼事情。這里只是簡單的設置了一個 reader 和一個 scanner,作用於 bean 的掃描工作。

我們再來看一下這個類的繼承關系

這里獲取 ExceptionReporter 的方式主要還是和之前 Listener 的方式一致,通過 getSpringFactoriesInstances 來獲取所有的 SpringBootExceptionReporter 。

其主要方法執行如下:

3. 怎麼查看一個springboot項目的源代碼在哪

要查看基於Spring Boot的Java項目的代碼,您需要進行以下步驟:

  • 查看項目結構:您可以在部署項目的位置找到項目文件夾。打開項目文件夾並查看項目結構。您可以找到源代碼文件夾(通常是「src」文件夾),該文件夾中包含Java源代碼辯慶文件。您也可以找到配置文件,資源文件和其他相關文件。

  • 查看源代碼:在源代碼文件夾中,您可以找到Java文件,這些文件包含應用程序的代碼邏輯。您可以使用任何文本編輯器或IDE(集成開發環境)打開這些文件。IDE(如Eclipse、IntelliJ IDEA)通常提供更好的開發環境和代碼閱讀功能。

  • 尋找關鍵詞:如果您不知道源代碼的特定位置,請嘗試在項目中搜索關鍵詞或特定函數。您可以使用文本編攜慶握輯器或IDE的搜索功能來搜索關鍵詞。

  • 調試代碼:如果您想深入了解代碼差胡的實際執行過程,可以使用IDE的調試工具。在調試模式下,您可以單步執行代碼,並查看變數值和執行結果。

  • 總之,查看基於Spring Boot的Java項目代碼需要一定的編程知識和經驗。如果您不熟悉Java編程語言或Spring框架,建議您先學習這些知識,然後再嘗試查看項目代碼。

熱點內容
消費券腳本 發布:2024-07-27 13:23:13 瀏覽:776
ip電腦伺服器未響應如何處理 發布:2024-07-27 13:22:53 瀏覽:381
m6傳祺2021款買哪個配置 發布:2024-07-27 13:22:04 瀏覽:461
emojijava 發布:2024-07-27 12:57:07 瀏覽:156
編程培訓福州 發布:2024-07-27 12:28:06 瀏覽:876
哈弗h6女生適合哪個配置 發布:2024-07-27 12:10:52 瀏覽:954
memcached啟動腳本 發布:2024-07-27 11:55:41 瀏覽:558
電動車怎麼看配置 發布:2024-07-27 11:55:05 瀏覽:238
mfc打開默認文件夾 發布:2024-07-27 11:41:23 瀏覽:648
電腦找不到伺服器的原因 發布:2024-07-27 11:33:58 瀏覽:864