本章開始來學(xué)習(xí)下Spring的源碼,看看Spring框架最核心、最常用的功能是怎么實(shí)現(xiàn)的。
網(wǎng)上介紹Spring,說源碼的文章,大多數(shù)都是生搬硬推,都是直接看來的觀點(diǎn)換個(gè)描述就放出來。這并不能說有問題,但沒有從一個(gè)很好的、容易切入的角度去了解學(xué)習(xí)。博主來嘗試拋棄一些所知,從使用上入手,步步回溯源碼去了解學(xué)習(xí)。
很多人會(huì)混亂IOC和DI的兩個(gè)概念,其實(shí)這兩者是層面的不同。
具體的區(qū)別的區(qū)別:IOC是DI的原理。依賴注入是向某個(gè)類或方法注入一個(gè)值,其中所用到的原理就是控制反轉(zhuǎn)。
所以說到操作層面的時(shí)候用DI,原理層的是說IOC,下文亦同。
對于DI最新使用方法,現(xiàn)在都是建議用Java注解去標(biāo)識(shí)。但是相信筆者,不要用這種方式去看源碼。筆者本來是想從Java注解入手去一步步看源碼,debug看看發(fā)生什么了。但發(fā)現(xiàn)更多時(shí)間是在調(diào)SpringBoot和AOP的源碼。在看了一天后,還是換一種思路吧,因?yàn)锳OP是打算在下一章再講的。
所以我用XML的方式,搭了一個(gè)最簡單的Spring項(xiàng)目來學(xué)習(xí)其中IOC的源碼。建議大家把代碼拉下來,跟著筆者思路來一起看。
源碼在此:https://github.com/Zack-Ku/spring-ioc-demo
搭建內(nèi)容
maven的依賴,只添加了spring-context模板,用的是4.3.11版本(部分代碼)
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>
</dependencies>
作為Bean的Service(部分代碼)
public class TestBeanServiceImpl implements TestBeanService {
public String getBean() {
return "a test bean";
}
}
配置XML(部分代碼)
<bean id="testBeanService" class="com.zack.demo.TestBeanServiceImpl"/>
啟動(dòng)類。只是加載了下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());
}
}
最后啟動(dòng)就能獲取這個(gè)bean,看到getMessage()打印的內(nèi)容了。
這樣就是一個(gè)比較純粹的Spring-IOC的項(xiàng)目了。我們直接從啟動(dòng)類開始看起
Bean的含義
前置先解釋下這個(gè)Bean的含義,因?yàn)闀?huì)貫穿整個(gè)流程。
通俗地講,Bean就是IOC的容器。如上面的例子,將TestBeanService注冊到Spring里,那么TestBeanService就是Spring的里面的一個(gè)Bean。Demo里面context.getBean()就是從Spring中取出這個(gè)Bean,完成控制反轉(zhuǎn)的。
所以我們的重點(diǎn)就是要看看Spring到底是怎么生成管理這些Bean的。
ClassPathXmlApplicationContext
啟動(dòng)類中,加載配置的ClassPathXmlApplicationContext肯定就是完成IOC的核心。不知道它到底是怎么做的,怎么入手呢?
先來看看它的類圖
先分析下這個(gè)類圖,
-
ClassPathXmlApplicationContext類是AbstractApplicationContext抽象類的子類 -
AbstractApplicationContext類是ApplicaionContext接口的實(shí)現(xiàn)。 -
ApplicaionContext接口集合了非常多的內(nèi)容,其中和IOC比較相關(guān)的就是ListableBeanFactory接口和HierarchicalBeanFactory接口 -
ListableBeanFactory接口和HierarchicalBeanFactory接口是繼承BeanFactory
從此分析可以看出,ClassPathXmlApplicationContext是什么,了解下ApplicaionContext;它怎么和IOC有關(guān),要了解BeanFactory。
所以后面我們先來看看ApplicaionContext與BeanFactory。
ApplicationContext
從該接口的注解描述可知,ApplicationContext是整個(gè)項(xiàng)目的配置,Spring項(xiàng)目在啟動(dòng)或運(yùn)行的時(shí)候都需要依賴到它。
其中Bean管理相關(guān)的則是ListableBeanFactory和HierarchicalBeanFactory。
BeanFactory
ListableBeanFactory和HierarchicalBeanFactory都是繼承BeanFactory的。
先看看BeanFactory的文件注解
從上圖可知,
BeanFactory就是獲取Bean容器的地方。而且他可以提供單例的對象或者是獨(dú)立的對象
從這段可以得知,
HierarchicalBeanFactory是一個(gè)分層的Bean,如果實(shí)現(xiàn)了這個(gè)接口,所有方法都會(huì)經(jīng)過父類的工廠。所以這個(gè)是個(gè)拓展的類,暫時(shí)先不看它。
接下來看看ListableBeanFactory注解說明
這個(gè)接口是要實(shí)現(xiàn)預(yù)先加載Bean的配置,生成好實(shí)例,直接管理Bean的實(shí)例,而不是來一個(gè)請求,生成一個(gè)。
好了,以上就是基本的概念和認(rèn)知,現(xiàn)在帶著這些概念,我們回頭看看ClassPathXmlApplicationContext的執(zhí)行流程,看看它到底怎么的生成管理Bean的。
初始化IOC容器
從ClassPathXmlApplicationContext的構(gòu)造函數(shù)看,最核心的就是refresh()函數(shù),其他只是設(shè)一些值。
而這個(gè)refresh()是調(diào)用父類AbstractApplicationContext中的refresh()。
根據(jù)它的注解可知它是加載刷新了整個(gè)context,并且加載所有Bean定義和創(chuàng)建對應(yīng)的單例。
看下這個(gè)方法做了什么
里面有許多步驟,重點(diǎn)看下
obtainFreshBeanFactory()(重新獲取一個(gè)BeanFactory)。它里面有個(gè)核心的方法
refreshBeanFactory()如果已有BeanFactory,先刪除所有Bean,然后關(guān)閉BeanFactory。
然后創(chuàng)建一個(gè)新的
ListableBeanFactory,上面說到這個(gè)工廠里會(huì)預(yù)先加載所有的Bean。最后核心的就是
loadBeanDefinitions(beanFactory),它是加載Bean的定義。實(shí)現(xiàn)交給了子類。用的是
XmlBeanDefinitionReader直接讀配置文件加載Bean Definition(Bean定義)到BeanFactory。它里面一步步把xml的配置文件拆解讀取,把一個(gè)個(gè)Bean Definition加載到BeanFactory里。至此,已經(jīng)有用一個(gè)加載好Bean Definition的BeanFactory了。
其他方法也是圍繞BeanFactory后置處理和Context的配置準(zhǔn)備。內(nèi)容太多,想更深入了解的話建議順著以上思路,找到對應(yīng)代碼閱讀以下。
依賴注入
回到啟動(dòng)類中,看看怎么從context中獲取bean的。
context.getBean(TestBeanService.class)
是根據(jù)類去拿bean的,當(dāng)然也可以根據(jù)id。
其對應(yīng)的源碼實(shí)現(xiàn),在DefaultListableBeanFactory中,上文有說到對應(yīng)的BeanFactory選型。
NamedBeanHolder是里面包含一個(gè)實(shí)例化的對象,和bean的名字。resolveNamedBean()是怎么拿出Bean的關(guān)鍵。
一步步Debug,可以看到,它是遍歷BeanFactory里面維護(hù)的beanDefinitionNames和manualSingletonNames成員變量,找出命中的beanName返回。
然后拿著這個(gè)beanName去找具體的bean實(shí)例。這里的代碼比較長,在
AbstractBeanFactory里面的doGetBean()中實(shí)現(xiàn)。大意是先嘗試去找手動(dòng)添加bean的單例工廠里找有沒有對應(yīng)的實(shí)例,沒有的話就往父類beanFactory里面找,最后沒有的話就生成一個(gè)。
spring中一個(gè)bean是如何加載和如何注入大致如此,更細(xì)節(jié)的內(nèi)容,可以自己debug看看源碼。
控制反轉(zhuǎn)的優(yōu)點(diǎn)
最后來以我個(gè)人觀點(diǎn)談?wù)効刂品崔D(zhuǎn)的優(yōu)點(diǎn)吧。
舉個(gè)例子,我要裝修房子,需要門、浴具、廚具、油漆、玻璃等材料。
decorateHouse(Door,BathThing,CookThing,....)
但是我作為一個(gè)裝修工人,我需要去制造門、制造浴具,合成玻璃油漆嗎?
不需要,也不關(guān)心其建造的過程,對應(yīng)的會(huì)有人去做這些東西。
door = buildDoor();
glass = buildGlass();
所有材料放到建材商城里面,裝修工人需要什么材料就去建材商城里面取。
對應(yīng)Spring的IOC,門、玻璃等材料就是Bean,建材商城就是IOC容器,把材料放到建材商城就是Bean加載,去商城拿材料就是依賴注入的過程。
程序開發(fā)發(fā)展至今,一個(gè)簡答的項(xiàng)目或許也要分幾個(gè)模板,幾個(gè)人去開發(fā)。劃分好職責(zé),設(shè)計(jì)好接口,面向接口編程。每個(gè)人只需要完成好自己那部分的工作,依賴調(diào)用就可以了。這樣做同時(shí)有助于降低項(xiàng)目的耦合度,讓項(xiàng)目有更好的延伸性。由此Spring的IOC就是基于以上的需求所誕生的。
總結(jié)
回顧下全文的內(nèi)容
- ApplicationContext是Spring項(xiàng)目的核心配置,項(xiàng)目運(yùn)行依賴于它,其中包含許多方面的內(nèi)容。
- BeanFactory是Context包含的內(nèi)容之一,它負(fù)責(zé)管理Bean的加載,生成,注入等內(nèi)容。
- Spring控制反轉(zhuǎn)為了降低項(xiàng)目耦合,提高延伸性。
本文講Spring IOC還比較淺顯,僅僅講了如何加載的重點(diǎn)和注入的重點(diǎn),關(guān)于生命周期,BeanFactory的處理由于篇幅問題并沒有細(xì)講。有興趣的讀者可以用Demo跑起來,一步步Debug看看。因?yàn)镈emo基本是最小化的Spring IOC了,所以這個(gè)Debug不會(huì)太難,很容易就能看清楚整個(gè)流程做了什么。
Demo:https://github.com/Zack-Ku/spring-ioc-demo
如果覺得還不錯(cuò),請關(guān)注微信公眾號(hào):Zack說碼