零基礎帶你看Spring源碼——IOC控制反轉

本章開始來學習下Spring的源碼,看看Spring框架最核心、最常用的功能是怎麼實現的。
網上介紹Spring,說源碼的文章,大多數都是生搬硬推,都是直接看來的觀點換個描述就放出來。這並不能說有問題,但沒有從一個很好的、容易切入的角度去了解學習。博主來嘗試拋棄一些所知,從使用上入手,步步回溯源碼去了解學習。

很多人會混亂IOC和DI的兩個概念,其實這兩者是層面的不同。
具體的區別的區別:IOC是DI的原理。依賴注入是向某個類或方法注入一個值,其中所用到的原理就是控制反轉。
所以說到操作層面的時候用DI,原理層的是說IOC,下文亦同。

對於DI最新使用方法,現在都是建議用Java註解去標識。但是相信筆者,不要用這種方式去看源碼。筆者本來是想從Java註解入手去一步步看源碼,debug看看發生什麼了。但發現更多時間是在調SpringBoot和AOP的源碼。在看了一天後,還是換一種思路吧,因爲AOP是打算在下一章再講的。

所以我用XML的方式,搭了一個最簡單的Spring項目來學習其中IOC的源碼。建議大家把代碼拉下來,跟着筆者思路來一起看。
源碼在此:https://github.com/Zack-Ku/spring-ioc-demo

搭建內容

maven的依賴,只添加了spring-context模板,用的是4.3.11版本<!-- more -->(部分代碼)

     <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"/>

啓動類。只是加載了下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的含義,因爲會貫穿整個流程。
通俗地講,Bean就是IOC的容器。如上面的例子,將TestBeanService註冊到Spring裏,那麼TestBeanService就是Spring的裏面的一個Bean。Demo裏面context.getBean()就是從Spring中取出這個Bean,完成控制反轉的。

所以我們的重點就是要看看Spring到底是怎麼生成管理這些Bean的。

ClassPathXmlApplicationContext

啓動類中,加載配置的ClassPathXmlApplicationContext肯定就是完成IOC的核心。不知道它到底是怎麼做的,怎麼入手呢?
先來看看它的類圖
零基礎帶你看Spring源碼——IOC控制反轉
先分析下這個類圖,

  1. ClassPathXmlApplicationContext類是AbstractApplicationContext抽象類的子類
  2. AbstractApplicationContext類是ApplicaionContext接口的實現。
  3. ApplicaionContext接口集合了非常多的內容,其中和IOC比較相關的就是ListableBeanFactory接口和HierarchicalBeanFactory接口
  4. ListableBeanFactory接口和HierarchicalBeanFactory接口是繼承BeanFactory

從此分析可以看出,ClassPathXmlApplicationContext是什麼,瞭解下ApplicaionContext;它怎麼和IOC有關,要了解BeanFactory
所以後面我們先來看看ApplicaionContextBeanFactory

ApplicationContext

零基礎帶你看Spring源碼——IOC控制反轉
從該接口的註解描述可知,ApplicationContext是整個項目的配置,Spring項目在啓動或運行的時候都需要依賴到它。

其中Bean管理相關的則是ListableBeanFactoryHierarchicalBeanFactory

BeanFactory

ListableBeanFactoryHierarchicalBeanFactory都是繼承BeanFactory的。
先看看BeanFactory的文件註解
零基礎帶你看Spring源碼——IOC控制反轉
從上圖可知,BeanFactory就是獲取Bean容器的地方。而且他可以提供單例的對象或者是獨立的對象

零基礎帶你看Spring源碼——IOC控制反轉
從這段可以得知,HierarchicalBeanFactory是一個分層的Bean,如果實現了這個接口,所有方法都會經過父類的工廠。所以這個是個拓展的類,暫時先不看它。

接下來看看ListableBeanFactory註解說明
零基礎帶你看Spring源碼——IOC控制反轉
這個接口是要實現預先加載Bean的配置,生成好實例,直接管理Bean的實例,而不是來一個請求,生成一個。

好了,以上就是基本的概念和認知,現在帶着這些概念,我們回頭看看ClassPathXmlApplicationContext的執行流程,看看它到底怎麼的生成管理Bean的。

初始化IOC容器

ClassPathXmlApplicationContext的構造函數看,最核心的就是refresh()函數,其他只是設一些值。
而這個refresh()是調用父類AbstractApplicationContext中的refresh()
根據它的註解可知它是加載刷新了整個context,並且加載所有Bean定義和創建對應的單例。
零基礎帶你看Spring源碼——IOC控制反轉

看下這個方法做了什麼
零基礎帶你看Spring源碼——IOC控制反轉
裏面有許多步驟,重點看下obtainFreshBeanFactory()(重新獲取一個BeanFactory)。
它裏面有個核心的方法refreshBeanFactory()
零基礎帶你看Spring源碼——IOC控制反轉
如果已有BeanFactory,先刪除所有Bean,然後關閉BeanFactory。
然後創建一個新的ListableBeanFactory,上面說到這個工廠裏會預先加載所有的Bean。
最後核心的就是loadBeanDefinitions(beanFactory),它是加載Bean的定義。實現交給了子類。
零基礎帶你看Spring源碼——IOC控制反轉
用的是XmlBeanDefinitionReader直接讀配置文件加載Bean Definition(Bean定義)到BeanFactory。它裏面一步步把xml的配置文件拆解讀取,把一個個Bean Definition加載到BeanFactory裏。
至此,已經有用一個加載好Bean Definition的BeanFactory了。

其他方法也是圍繞BeanFactory後置處理和Context的配置準備。內容太多,想更深入瞭解的話建議順着以上思路,找到對應代碼閱讀以下。

依賴注入

回到啓動類中,看看怎麼從context中獲取bean的。

    context.getBean(TestBeanService.class)

是根據類去拿bean的,當然也可以根據id。
其對應的源碼實現,在DefaultListableBeanFactory中,上文有說到對應的BeanFactory選型。
零基礎帶你看Spring源碼——IOC控制反轉
NamedBeanHolder是裏面包含一個實例化的對象,和bean的名字。resolveNamedBean()是怎麼拿出Bean的關鍵。

一步步Debug,可以看到,它是遍歷BeanFactory裏面維護的beanDefinitionNames和manualSingletonNames成員變量,找出命中的beanName返回。
零基礎帶你看Spring源碼——IOC控制反轉
然後拿着這個beanName去找具體的bean實例。這裏的代碼比較長,在AbstractBeanFactory裏面的doGetBean()中實現。
大意是先嚐試去找手動添加bean的單例工廠裏找有沒有對應的實例,沒有的話就往父類beanFactory裏面找,最後沒有的話就生成一個。

spring中一個bean是如何加載和如何注入大致如此,更細節的內容,可以自己debug看看源碼。

控制反轉的優點

最後來以我個人觀點談談控制反轉的優點吧。
舉個例子,我要裝修房子,需要門、浴具、廚具、油漆、玻璃等材料。

    decorateHouse(Door,BathThing,CookThing,....)

但是我作爲一個裝修工人,我需要去製造門、製造浴具,合成玻璃油漆嗎?
不需要,也不關心其建造的過程,對應的會有人去做這些東西。

    door = buildDoor();
    glass = buildGlass(); 

所有材料放到建材商城裏面,裝修工人需要什麼材料就去建材商城裏面取。

對應Spring的IOC,門、玻璃等材料就是Bean,建材商城就是IOC容器,把材料放到建材商城就是Bean加載,去商城拿材料就是依賴注入的過程。

程序開發發展至今,一個簡答的項目或許也要分幾個模板,幾個人去開發。劃分好職責,設計好接口,面向接口編程。每個人只需要完成好自己那部分的工作,依賴調用就可以了。這樣做同時有助於降低項目的耦合度,讓項目有更好的延伸性。由此Spring的IOC就是基於以上的需求所誕生的。

總結

回顧下全文的內容

  1. ApplicationContext是Spring項目的核心配置,項目運行依賴於它,其中包含許多方面的內容。
  2. BeanFactory是Context包含的內容之一,它負責管理Bean的加載,生成,注入等內容。
  3. Spring控制反轉爲了降低項目耦合,提高延伸性。

本文講Spring IOC還比較淺顯,僅僅講了如何加載的重點和注入的重點,關於生命週期,BeanFactory的處理由於篇幅問題並沒有細講。有興趣的讀者可以用Demo跑起來,一步步Debug看看。因爲Demo基本是最小化的Spring IOC了,所以這個Debug不會太難,很容易就能看清楚整個流程做了什麼。

Demo:https://github.com/Zack-Ku/spring-ioc-demo


更多技術文章、精彩乾貨,請關注
博客:zackku.com
微信公衆號:Zack說碼
零基礎帶你看Spring源碼——IOC控制反轉

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章