本章開始來學習下Spring的源碼,看看Spring框架最核心、最經常使用的功能是怎麼實現的。
網上介紹Spring,說源碼的文章,大多數都是生搬硬推,都是直接看來的觀點換個描述就放出來。這並不能說有問題,但沒有從一個很好的、容易切入的角度去了解學習。博主來嘗試拋棄一些所知,從使用上入手,步步回溯源碼去了解學習。git
不少人會混亂IOC和DI的兩個概念,其實這二者是層面的不一樣。
具體的區別的區別:IOC是DI的原理。依賴注入是向某個類或方法注入一個值,其中所用到的原理就是控制反轉。
因此說到操做層面的時候用DI,原理層的是說IOC,下文亦同。github
對於DI最新使用方法,如今都是建議用Java註解去標識。可是相信筆者,不要用這種方式去看源碼。筆者原本是想從Java註解入手去一步步看源碼,debug看看發生什麼了。但發現更多時間是在調SpringBoot和AOP的源碼。在看了一天後,仍是換一種思路吧,由於AOP是打算在下一章再講的。spring
因此我用XML的方式,搭了一個最簡單的Spring項目來學習其中IOC的源碼。建議你們把代碼拉下來,跟着筆者思路來一塊兒看。
源碼在此:https://github.com/Zack-Ku/sp...編程
maven的依賴,只添加了spring-context模板,用的是4.3.11版本<!-- more -->(部分代碼)app
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.11.RELEASE</version> </dependency> </dependencies>
<!-- -->框架
做爲Bean的Service(部分代碼)maven
public class TestBeanServiceImpl implements TestBeanService { public String getBean() { return "a test bean"; } }
<!-- -->函數
配置XML(部分代碼)學習
<bean id="testBeanService" class="com.zack.demo.TestBeanServiceImpl"/>
<!-- -->ui
啓動類。只是加載了下spring的xml配置,而後從context中拿出Bean,這就是完整IOC的過程了。(部分代碼)
public class Application { public static void main(String[] args) { // 加載xml配置 ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml"); // IOC獲取Bean TestBeanService testBeanService = context.getBean(TestBeanService.class); System.out.println(testBeanService.getBean()); } }
<!-- -->
最後啓動就能獲取這個bean,看到getMessage()打印的內容了。
這樣就是一個比較純粹的Spring-IOC的項目了。咱們直接從啓動類開始看起
前置先解釋下這個Bean的含義,由於會貫穿整個流程。
通俗地講,Bean就是IOC的容器。如上面的例子,將TestBeanService註冊到Spring裏,那麼TestBeanService就是Spring的裏面的一個Bean。Demo裏面context.getBean()
就是從Spring中取出這個Bean,完成控制反轉的。
因此咱們的重點就是要看看Spring究竟是怎麼生成管理這些Bean的。
啓動類中,加載配置的ClassPathXmlApplicationContext
確定就是完成IOC的核心。不知道它究竟是怎麼作的,怎麼入手呢?
先來看看它的類圖
先分析下這個類圖,
ClassPathXmlApplicationContext
類是AbstractApplicationContext
抽象類的子類AbstractApplicationContext
類是ApplicaionContext
接口的實現。ApplicaionContext
接口集合了很是多的內容,其中和IOC比較相關的就是ListableBeanFactory
接口和HierarchicalBeanFactory
接口ListableBeanFactory
接口和HierarchicalBeanFactory
接口是繼承BeanFactory
今後分析能夠看出,ClassPathXmlApplicationContext
是什麼,瞭解下ApplicaionContext
;它怎麼和IOC有關,要了解BeanFactory
。
因此後面咱們先來看看ApplicaionContext
與BeanFactory
。
從該接口的註解描述可知,ApplicationContext是整個項目的配置,Spring項目在啓動或運行的時候都須要依賴到它。
其中Bean管理相關的則是ListableBeanFactory
和HierarchicalBeanFactory
。
ListableBeanFactory
和HierarchicalBeanFactory
都是繼承BeanFactory
的。
先看看BeanFactory
的文件註解
從上圖可知,BeanFactory
就是獲取Bean容器的地方。並且他能夠提供單例的對象或者是獨立的對象
從這段能夠得知,HierarchicalBeanFactory
是一個分層的Bean,若是實現了這個接口,全部方法都會通過父類的工廠。因此這個是個拓展的類,暫時先不看它。
接下來看看ListableBeanFactory
註解說明
這個接口是要實現預先加載Bean的配置,生成好實例,直接管理Bean的實例,而不是來一個請求,生成一個。
好了,以上就是基本的概念和認知,如今帶着這些概念,咱們回頭看看ClassPathXmlApplicationContext
的執行流程,看看它到底怎麼的生成管理Bean的。
從ClassPathXmlApplicationContext
的構造函數看,最核心的就是refresh()
函數,其餘只是設一些值。
而這個refresh()
是調用父類AbstractApplicationContext
中的refresh()
。
根據它的註解可知它是加載刷新了整個context,而且加載全部Bean定義和建立對應的單例。
看下這個方法作了什麼
裏面有許多步驟,重點看下obtainFreshBeanFactory()
(從新獲取一個BeanFactory)。
它裏面有個核心的方法refreshBeanFactory()
若是已有BeanFactory,先刪除全部Bean,而後關閉BeanFactory。
而後建立一個新的ListableBeanFactory
,上面說到這個工廠裏會預先加載全部的Bean。
最後核心的就是loadBeanDefinitions(beanFactory)
,它是加載Bean的定義。實現交給了子類。
用的是XmlBeanDefinitionReader
直接讀配置文件加載Bean Definition(Bean定義)到BeanFactory。它裏面一步步把xml的配置文件拆解讀取,把一個個Bean Definition加載到BeanFactory裏。
至此,已經有用一個加載好Bean Definition的BeanFactory了。
其餘方法也是圍繞BeanFactory後置處理和Context的配置準備。內容太多,想更深刻了解的話建議順着以上思路,找到對應代碼閱讀如下。
回到啓動類中,看看怎麼從context中獲取bean的。
context.getBean(TestBeanService.class)
是根據類去拿bean的,固然也能夠根據id。
其對應的源碼實現,在DefaultListableBeanFactory
中,上文有說到對應的BeanFactory選型。NamedBeanHolder
是裏面包含一個實例化的對象,和bean的名字。resolveNamedBean()
是怎麼拿出Bean的關鍵。
一步步Debug,能夠看到,它是遍歷BeanFactory裏面維護的beanDefinitionNames和manualSingletonNames成員變量,找出命中的beanName返回。
而後拿着這個beanName去找具體的bean實例。這裏的代碼比較長,在AbstractBeanFactory
裏面的doGetBean()
中實現。
大意是先嚐試去找手動添加bean的單例工廠裏找有沒有對應的實例,沒有的話就往父類beanFactory裏面找,最後沒有的話就生成一個。
spring中一個bean是如何加載和如何注入大體如此,更細節的內容,能夠本身debug看看源碼。
最後來以我我的觀點談談控制反轉的優勢吧。
舉個例子,我要裝修房子,須要門、浴具、廚具、油漆、玻璃等材料。
decorateHouse(Door,BathThing,CookThing,....)
可是我做爲一個裝修工人,我須要去製造門、製造浴具,合成玻璃油漆嗎?
不須要,也不關心其建造的過程,對應的會有人去作這些東西。
door = buildDoor(); glass = buildGlass();
全部材料放到建材商城裏面,裝修工人須要什麼材料就去建材商城裏面取。
對應Spring的IOC,門、玻璃等材料就是Bean,建材商城就是IOC容器,把材料放到建材商城就是Bean加載,去商城拿材料就是依賴注入的過程。
程序開發發展至今,一個簡答的項目或許也要分幾個模板,幾我的去開發。劃分好職責,設計好接口,面向接口編程。每一個人只須要完成好本身那部分的工做,依賴調用就能夠了。這樣作同時有助於下降項目的耦合度,讓項目有更好的延伸性。由此Spring的IOC就是基於以上的需求所誕生的。
回顧下全文的內容
本文講Spring IOC還比較淺顯,僅僅講了如何加載的重點和注入的重點,關於生命週期,BeanFactory的處理因爲篇幅問題並無細講。有興趣的讀者能夠用Demo跑起來,一步步Debug看看。由於Demo基本是最小化的Spring IOC了,因此這個Debug不會太難,很容易就能看清楚整個流程作了什麼。
Demo:https://github.com/Zack-Ku/sp...
若是以爲還不錯,請關注公衆號:Zack說碼