註意,看完這篇文章需要很長很長很長時間。。。 準備工作 本文會分析Spring的IOC模塊的整體流程,分析過程需要使用一個簡單的demo工程來啟動Spring,demo工程我以備好,需要的童鞋自行在下方鏈接下載: 1 https://github.com/shiyujun/spring-framew ...
註意,看完這篇文章需要很長很長很長時間。。。
準備工作
本文會分析Spring的IOC模塊的整體流程,分析過程需要使用一個簡單的demo工程來啟動Spring,demo工程我以備好,需要的童鞋自行在下方鏈接下載:
1
|
https://github.com/shiyujun/spring-framework
|
Demo工程示例代碼
本文源碼分析基於Spring5.0.0,所以pom文件中引入5.0的依賴
1
|
<dependencies>
|
然後寫一個簡單的介面和實現類
1
|
public interface IOCService {
|
新建一個application-ioc.xml
1
|
|
啟動Spring
1
|
public class IOCDemo {
|
上方一個簡單的demo工程相信各位童鞋在剛剛學習Spring的時候就已經玩的特別6了。我就不詳細的說明瞭,直接開始看源碼吧
ClassPathXmlApplicationContext
背景調查
在文章開始的demo工程中,我選擇使用了一個xml文件來配置了介面和實現類之間的關係,然後使用了ClassPathXmlApplicationContext這個類來載入這個配置文件。現在我們就先來看一下這個類到底是個什麼東東
首先看一下繼承關係圖(只保留了跟本文相關的,省略了很多其他的繼承關係)
可以看到左下角的就是我們今天的主角ClassPathXmlApplicationContext、然後它的旁邊是一個同門師兄弟FileSystemXmlApplicationContext。看名字就可以知道它們哥倆都是通過載入配置文件來啟動Spring的,只不過一個是從程式內載入一個是從系統內載入。
除了這兩個還有一個類AnnotationConfigApplicationContext比較值得我們關註,這個類是用來處理註解式編程的。
而最上邊的ApplicationContext則是大名鼎鼎的Spring核心上下文了
源碼分析
看一下這個類的源代碼
1
|
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
|
可以看到整體來看源碼比較簡單,只有setConfigLocations
和refresh
兩個方法沒有看到具體的實現。但是如果你因為這個而小巧了Spring那可就大錯特錯了,setConfigLocations
只是一個開胃小菜,refresh
才是我們本文的重點
setConfigLocations
setConfigLocations
方法的主要工作有兩個:創建環境對象ConfigurableEnvironment和處理ClassPathXmlApplicationContext傳入的字元串中的占位符
跟著setConfigLocations
方法一直往下走
1
|
public void setConfigLocations(String... locations) {
|
這裡getEnironment()
就涉及到了創建環境變數相關的操作了
獲取環境變數
1
|
public ConfigurableEnvironment getEnvironment() {
|
看一下ConfigurableEnvironment
這個介面的繼承圖(1張沒能截全,兩張一塊看)
這個介面比較重要的就是兩部分內容了,一個是設置Spring的環境就是我們經常用的spring.profile配置。另外就是系統資源Property
接著看createEnvironment()
方法,發現它返回了一個StandardEnvironment
類,而這個類中的customizePropertySources
方法就會往資源列表中添加Java進程中的變數和系統的環境變數
1
|
protected void customizePropertySources(MutablePropertySources propertySources) {
|
處理占位符
再次回到 resolvePath
方法後跟進通過上方獲取的ConfigurableEnvironment
介面的resolveRequiredPlaceholders
方法,終點就是下方的這個方法。這個方法主要就是處理所有使用${}方式的占位符
1
|
protected String parseStringValue(
|
refresh
配置文件名稱解析完畢後,就到了最關鍵的一步refresh方法。這個方法,接下來會用超級長的篇幅來解析這個方法
先看一下這個方法里大致內容
1
|
public void refresh() throws BeansException, IllegalStateException {
|
是不是看著有點懵,不要著急,一行一行往下看,不研究明白誓不罷休
1. synchronized
為了避免refresh()
還沒結束,再次發起啟動或者銷毀容器引起的衝突
2. prepareRefresh()
做一些準備工作,記錄容器的啟動時間、標記“已啟動”狀態、檢查環境變數等
1
|
protected void prepareRefresh() {
|
其中檢查環境變數的核心方法為,簡單來說就是如果存在環境變數的value為空的時候就拋異常,然後停止啟動Spring
1
|
public void validateRequiredProperties() {
|
基於這個特性我們可以做一些擴展,提前在集合requiredProperties
中放入我們這個項目必須存在的一些環境變數。假說我們的生產環境資料庫地址、用戶名和密碼都是使用環境變數的方式註入進去來代替測試環境的配置,那麼就可以在這裡添加這個校驗,在程式剛啟動的時候就能發現問題
3. obtainFreshBeanFactory()
乍一看這個方法也沒幾行代碼,但是這個方法負責了BeanFactory的初始化、Bean的載入和註冊等事件
1
|
|
BeanFactory
先看refreshBeanFactory()
1
|
|
這裡一開始就實例化了一個DefaultListableBeanFactory,先看一下這個類的繼承關係
可以看到這個哥們的背景相當大,所有關於容器的介面、抽象類他都繼承了。再看他的方法
這方法簡直多的嚇人,妥妥的Spring家族超級富二代。看他的方法名稱相信就可以猜出他大部分的功能了
BeanDefinition
在看loadBeanDefinitions()
這個方法之前,就必須瞭解一個東西了。那就是:BeanDefinition
我們知道BeanFactory是一個Bean容器,而BeanDefinition就是Bean的一種形式(它裡面包含了Bean指向的類、是否單例、是否懶載入、Bean的依賴關係等相關的屬性)。BeanFactory中就是保存的BeanDefinition。
看BeanDefinition的介面定義
1
|
|
讀取配置文件
現在可以看loadBeanDefinitions()
方法了,這個方法會根據配置,載入各個 Bean,然後放到 BeanFactory 中
1
|
|
1
|
|
第一個if是看有沒有系統指定的配置文件,如果沒有的話就走第二個if載入我們最開始傳入的classpath:application-ioc.xml
1
|
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { |