零基礎(chǔ)帶你看Spring源碼——IOC控制反轉(zhuǎn)

本章開始來學(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的核心。不知道它到底是怎么做的,怎么入手呢?
先來看看它的類圖

image

先分析下這個(gè)類圖,

  1. ClassPathXmlApplicationContext類是AbstractApplicationContext抽象類的子類
  2. AbstractApplicationContext類是ApplicaionContext接口的實(shí)現(xiàn)。
  3. ApplicaionContext接口集合了非常多的內(nèi)容,其中和IOC比較相關(guān)的就是ListableBeanFactory接口和HierarchicalBeanFactory接口
  4. ListableBeanFactory接口和HierarchicalBeanFactory接口是繼承BeanFactory

從此分析可以看出,ClassPathXmlApplicationContext是什么,了解下ApplicaionContext;它怎么和IOC有關(guān),要了解BeanFactory
所以后面我們先來看看ApplicaionContextBeanFactory。

ApplicationContext

image

從該接口的注解描述可知,ApplicationContext是整個(gè)項(xiàng)目的配置,Spring項(xiàng)目在啟動(dòng)或運(yùn)行的時(shí)候都需要依賴到它。

其中Bean管理相關(guān)的則是ListableBeanFactoryHierarchicalBeanFactory。

BeanFactory

ListableBeanFactoryHierarchicalBeanFactory都是繼承BeanFactory的。
先看看BeanFactory的文件注解

image

從上圖可知,BeanFactory就是獲取Bean容器的地方。而且他可以提供單例的對象或者是獨(dú)立的對象

image

從這段可以得知,HierarchicalBeanFactory是一個(gè)分層的Bean,如果實(shí)現(xiàn)了這個(gè)接口,所有方法都會(huì)經(jīng)過父類的工廠。所以這個(gè)是個(gè)拓展的類,暫時(shí)先不看它。

接下來看看ListableBeanFactory注解說明

image

這個(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)的單例。

image

看下這個(gè)方法做了什么

image

里面有許多步驟,重點(diǎn)看下obtainFreshBeanFactory()(重新獲取一個(gè)BeanFactory)。
它里面有個(gè)核心的方法refreshBeanFactory()
image

如果已有BeanFactory,先刪除所有Bean,然后關(guān)閉BeanFactory。
然后創(chuàng)建一個(gè)新的ListableBeanFactory,上面說到這個(gè)工廠里會(huì)預(yù)先加載所有的Bean。
最后核心的就是loadBeanDefinitions(beanFactory),它是加載Bean的定義。實(shí)現(xiàn)交給了子類。
image

用的是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選型。

image

NamedBeanHolder是里面包含一個(gè)實(shí)例化的對象,和bean的名字。resolveNamedBean()是怎么拿出Bean的關(guān)鍵。

一步步Debug,可以看到,它是遍歷BeanFactory里面維護(hù)的beanDefinitionNames和manualSingletonNames成員變量,找出命中的beanName返回。

image

然后拿著這個(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)容

  1. ApplicationContext是Spring項(xiàng)目的核心配置,項(xiàng)目運(yùn)行依賴于它,其中包含許多方面的內(nèi)容。
  2. BeanFactory是Context包含的內(nèi)容之一,它負(fù)責(zé)管理Bean的加載,生成,注入等內(nèi)容。
  3. 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說碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容