代碼入口
之前寫文章都會啰啰嗦嗦一大堆再開始,進入【Spring源碼分析】這個闆塊就直接切入正題了。
很多朋友可能想看Spring源碼,但是不知道應當如何入手去看,這個可以了解:Java開發者通常從事的都是Java Web的工作,對于程式員來說,一個Web項目用到Spring,隻是配置一下配置檔案而已,Spring的加載過程相對是不太透明的,不太好去找加載的代碼入口。
下面有很簡單的一段代碼可以作為Spring代碼加載的入口:
1 2 | |
ClassPathXmlApplicationContext用于加載CLASSPATH下的Spring配置檔案,可以看到,第二行就已經可以擷取到Bean的執行個體了,那麼必然第一行就已經完成了對所有Bean執行個體的加載,是以可以通過ClassPathXmlApplicationContext作為入口。為了後面便于代碼閱讀,先給出一下ClassPathXmlApplicationContext這個類的繼承關系:

大緻的繼承關系是如上圖所示的,由于版面的關系,沒有繼續畫下去了,左下角的ApplicationContext應當還有一層繼承關系,比較關鍵的一點是它是BeanFactory的子接口。
最後聲明一下,本文使用的Spring版本為3.0.7,比較老,使用這個版本純粹是因為公司使用而已。
ClassPathXmlApplicationContext構造函數
看下ClassPathXmlApplicationContext的構造函數:
|
1 2 3 4 5 6 7 8 9 | |
從第二段代碼看,總共就做了三件事:
1、super(parent)
沒什麼太大的作用,設定一下父級ApplicationContext,這裡是null
2、setConfigLocations(configLocations)
代碼就不貼了,一看就知道,裡面做了兩件事情:
(1)将指定的Spring配置檔案的路徑存儲到本地
(2)解析Spring配置檔案路徑中的${PlaceHolder}占位符,替換為系統變量中PlaceHolder對應的Value值,System本身就自帶一些系統變量比如class.path、os.name、user.dir等,也可以通過System.setProperty()方法設定自己需要的系統變量
3、refresh()
這個就是整個Spring Bean加載的核心了,它是ClassPathXmlApplicationContext的父類AbstractApplicationContext的一個方法,顧名思義,用于重新整理整個Spring上下文資訊,定義了整個Spring上下文加載的流程。
refresh方法
上面已經說了,refresh()方法是整個Spring Bean加載的核心,是以看一下整個refresh()方法的定義:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |
每個子方法的功能之後一點一點再分析,首先refresh()方法有幾點是值得我們學習的:
1、方法是加鎖的,這麼做的原因是避免多線程同時重新整理Spring上下文
2、盡管加鎖可以看到是針對整個方法體的,但是沒有在方法前加synchronized關鍵字,而使用了對象鎖startUpShutdownMonitor,這樣做有兩個好處:
(1)refresh()方法和close()方法都使用了startUpShutdownMonitor對象鎖加鎖,這就保證了在調用refresh()方法的時候無法調用close()方法,反之亦然,避免了沖突
(2)另外一個好處不在這個方法中展現,但是提一下,使用對象鎖可以減小了同步的範圍,隻對不能并發的代碼塊進行加鎖,提高了整體代碼運作的效率
3、方法裡面使用了每個子方法定義了整個refresh()方法的流程,使得整個方法流程清晰易懂。這點是非常值得學習的,一個方法裡面幾十行甚至上百行代碼寫在一起,在我看來會有三個顯著的問題:
(1)擴充性降低。反過來講,假使把流程定義為方法,子類可以繼承父類,可以根據需要重寫方法
(2)代碼可讀性差。很簡單的道理,看代碼的人是願意看一段500行的代碼,還是願意看10段50行的代碼?
(3)代碼可維護性差。這點和上面的類似但又有不同,可維護性差的意思是,一段幾百行的代碼,功能點不明确,不易後人修改,可能會導緻“牽一發而動全身”
prepareRefresh方法
下面挨個看refresh方法中的子方法,首先是prepareRefresh方法,看一下源碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
這個方法功能比較簡單,顧名思義,準備重新整理Spring上下文,其功能注釋上寫了:
1、設定一下重新整理Spring上下文的開始時間
2、将active辨別位設定為true
另外可以注意一下12行這句日志,這句日志列印了真正加載Spring上下文的Java類。
obtainFreshBeanFactory方法
obtainFreshBeanFactory方法的作用是擷取重新整理Spring上下文的Bean工廠,其代碼實作為:
1 2 3 4 5 6 7 8 | |
其核心是第二行的refreshBeanFactory方法,這是一個抽象方法,有AbstractRefreshableApplicationContext和GenericApplicationContext這兩個子類實作了這個方法,看一下上面ClassPathXmlApplicationContext的繼承關系圖即知,調用的應當是AbstractRefreshableApplicationContext中實作的refreshBeanFactory,其源碼為:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
這段代碼的核心是第7行,這行點出了DefaultListableBeanFactory這個類,這個類是構造Bean的核心類,這個類的功能會在下一篇文章中詳細解讀,首先給出DefaultListableBeanFactory的繼承關系圖:
AbstractAutowireCapableBeanFactory這個類的繼承層次比較深,版面有限,就沒有繼續畫下去了,本圖基本上清楚地展示了DefaultListableBeanFactory的層次結構。
為了更清晰地說明DefaultListableBeanFactory的作用,列舉一下DefaultListableBeanFactory中存儲的一些重要對象及對象中的内容,DefaultListableBeanFactory基本就是操作這些對象,以表格形式說明:
對象名 | 類 型 | 作 用 | 歸屬類 |
aliasMap | Map<String, String> | 存儲Bean名稱->Bean别名映射關系 | SimpleAliasRegistry |
singletonObjects | Map<String, Object> | 存儲單例Bean名稱->單例Bean實作映射關系 | DefaultSingletonBeanRegistry |
singletonFactories | Map<String, ObjectFactory> | 存儲Bean名稱->ObjectFactory實作映射關系 | DefaultSingletonBeanRegistry |
earlySingletonObjects | Map<String, Object> | 存儲Bean名稱->預加載Bean實作映射關系 | DefaultSingletonBeanRegistry |
registeredSingletons | Set<String> | 存儲注冊過的Bean名 | DefaultSingletonBeanRegistry |
singletonsCurrentlyInCreation | Set<String> | 存儲目前正在建立的Bean名 | DefaultSingletonBeanRegistry |
disposableBeans | Map<String, Object> | 存儲Bean名稱->Disposable接口實作Bean實作映射關系 | DefaultSingletonBeanRegistry |
factoryBeanObjectCache | Map<String, Object> | 存儲Bean名稱->FactoryBean接口Bean實作映射關系 | FactoryBeanRegistrySupport |
propertyEditorRegistrars | Set<PropertyEditorRegistrar> | 存儲PropertyEditorRegistrar接口實作集合 | AbstractBeanFactory |
embeddedValueResolvers | List<StringValueResolver> | 存儲StringValueResolver(字元串解析器)接口實作清單 | AbstractBeanFactory |
beanPostProcessors | List<BeanPostProcessor> | 存儲 BeanPostProcessor接口實作清單 | AbstractBeanFactory |
mergedBeanDefinitions | Map<String, RootBeanDefinition> | 存儲Bean名稱->合并過的根Bean定義映射關系 | AbstractBeanFactory |
alreadyCreated | Set<String> | 存儲至少被建立過一次的Bean名集合 | AbstractBeanFactory |
ignoredDependencyInterfaces | Set<Class> | 存儲不自動注入的Class集合 | AbstractAutowireCapableBeanFactory |
resolvableDependencies | Map<Class, Object> | 存儲修正過的依賴映射關系 | DefaultListableBeanFactory |
beanDefinitionMap | Map<String, BeanDefinition> | 存儲Bean名稱–>Bean定義映射關系 | DefaultListableBeanFactory |
beanDefinitionNames | List<String> | 存儲Bean定義名稱清單 | DefaultListableBeanFactory |
XML檔案解析
另外一個核心是第10行,loadBeanDefinitions(beanFactory)方法,為什麼我們配置的XML檔案最後能轉成Java Bean,首先就是由這個方法處理的。該方法最終的目的是将XML檔案進行解析,以Key-Value的形式,Key表示BeanName,Value為BeanDefinition,最終存入DefaultListableBeanFactory中:
|
最終DefaultListableBeanFactory會先周遊beanDefinitionNames,從beanDefinitionMap中拿到對應的BeanDefinition,最終轉為具體的Bean對象。BeanDefinition本身是一個接口,AbstractBeanDefinition這個抽象類存儲了Bean的屬性,看一下AbstractBeanDefinition這個抽象類的定義:
這個類的屬性與方法很多,這裡就列舉了一些最主要的方法和屬性,可以看到包含了bean标簽中的所有屬性,之後就是根據AbstractBeanDefinition中的屬性值構造出對應的Bean對象。
Spring沒有直接拿到XML中的bean定義就直接轉為具體的Bean對象,就是給Spring開發者留下了擴充點,比如之前BeanPostProcessor,在最後一部分會簡單提及。接着看一下XML是如何轉為Bean的,首先在AbstractXmlApplicationContext中将DefaultListableBeanFactory轉換為XmlBeanDefinitionReader:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
XmlBeanDefinitionReader顧名思義,一個XML檔案中讀取Bean定義的工具,然後追蹤13行的代碼,先追蹤到DefaultBeanDefinitionDocumentReader的parseDefaultElement方法:
1 2 3 4 5 6 7 8 9 10 11 | |
XML檔案的節點import、alias、bean分别有自己對應的方法去處理,以最常見的Bean為例,即第9行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
核心的解析bean節點的代碼為第2行,這裡已經調用到了BeanDefinitionParserDelegate類的parseBeanDefinitionElement方法,看下是怎麼做的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | |
總結一下代碼邏輯:
(1)第2行和第3行,擷取id屬性和name屬性
(2)第5行~第9行,如果填寫了name屬性的話,将name屬性以”,;”,分割出來的字元串全部認為這個bean的别名,這裡我們可以學到Spring的StringUtils的tokenizeToStringArray方法,可以将字元串按照指定分割符分割為字元串數組
(3)第11行~第18行,預設beanName為id屬性,如果bean有配置别名(即上面的name屬性的話),以name屬性的第一個值作為beanName,發現很多人不知道beanName是什麼,這幾行代碼就表示了容器是如何定義beanName的
(4)第20行~第22行,這段用于保證beanName的唯一性的,BeanDefinitionParserDelegate中有一個屬性usedNames,這是一個Set,強制性地保證了beanName的唯一性
(5)第24行用于解析bean的其他屬性,後面的代碼不太重要,看一下parseBeanDefinitionElement的實作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | |
這裡會取class屬性、parent屬性,18行的代碼可以跟進去看一下這裡就不貼了,會取得scope、lazy-init、abstract、depends-on屬性等等,設定到BeanDefinition中,這樣大緻上,一個Bean的定義就被存入了BeanDefinition中。最後一步追溯到之前DefaultBeanDefinitionDocumentReader的processBeanDefinition方法:
1 | |
這句語句将BeanDefinition存入DefaultListableBeanFactory的beanDefinitionMap中,追蹤一下代碼最終到DefaultListableBeanFactory的registerBeanDefinition方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | |
大緻上就是beanDefinitionNames中增加一個beanName,beanDefinitionMap将老的BeanDefinition替換(假如不允許BeanDefinition重寫的話會抛出異常)。這樣一個漫長的流程過後,XML檔案中的各個bean節點被轉換為BeanDefinition,存入了DefaultListableBeanFactory中,後續DefaultListableBeanFactory可以根據BeanDefinition,構造對應的Bean對象出來。
<bean>中不定義id及id重複場景Spring的處理方式
這兩天又想到了一個細節問題,<bean>中不定義id或者id重複,這兩種場景Spring是如何處理的。首先看一下不定義id的場景,代碼在BeanDefinitionParserDelegate類第398行的這個判斷這裡:
1 2 3 4 5 6 7 8 9 10 11 | |
當bean的id未定義時,即beanName為空,進入第2行的if判斷。containingBean可以看一下,這裡是由方法傳入的,是一個null值,是以進入第9行的判斷,即beanName由第9行的方法生成,看一下生成方式,代碼最終要追蹤到BeanDefinitionReaderUtils的generateBeanName方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
這段代碼的邏輯很容易看懂,即:
- 假如是innerBean(比如Spring AOP産生的Bean),使用【類全路徑+#+對象HashCode的16進制】的格式來命名Bean
- 假如不是innerBean,使用【類全路徑+#+數字】的格式來命名Bean,其中數字指的是,同一個Bean出現1次,隻要該Bean沒有id,就從0開始依次向上累加,比如a.b.c#0、a.b.c#1、a.b.c#2
接着看一下id重複的場景Spring的處理方式,重複id是這樣的,Spring使用XmlBeanDefinitionReader讀取xml檔案,在這個類的doLoadBeanDefinitions的方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
第5行的代碼将xml解析成Document,這裡的解析使用的是JDK自帶的DocumentBuilder,DocumentBuilder處理xml檔案輸入流,發現兩個<bean>中定義的id重複即會抛出XNIException異常,最終将導緻Spring容器啟動失敗。
是以,結論就是:Spring不允許兩個<bean>定義相同的id。