1. 程式人生 > >零基礎帶你看Spring原始碼——IOC控制反轉

零基礎帶你看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版本(部分程式碼)

     <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的核心。不知道它到底是怎麼做的,怎麼入手呢?
先來看看它的類圖

先分析下這個類圖,

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

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

ApplicationContext


從該介面的註解描述可知,ApplicationContext是整個專案的配置,Spring專案在啟動或執行的時候都需要依賴到它。

其中Bean管理相關的則是ListableBeanFactoryHierarchicalBeanFactory

BeanFactory

ListableBeanFactoryHierarchicalBeanFactory都是繼承BeanFactory的。
先看看BeanFactory的檔案註解

從上圖可知,BeanFactory就是獲取Bean容器的地方。而且他可以提供單例的物件或者是獨立的物件


從這段可以得知,HierarchicalBeanFactory是一個分層的Bean,如果實現了這個介面,所有方法都會經過父類的工廠。所以這個是個拓展的類,暫時先不看它。

接下來看看ListableBeanFactory註解說明

這個介面是要實現預先載入Bean的配置,生成好例項,直接管理Bean的例項,而不是來一個請求,生成一個。

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

初始化IOC容器

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就是基於以上的需求所誕生的。

總結

回顧下全文的內容

  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說碼