長話短說Spring(1)之IoC控制反轉(zhuǎn)

簡書 Wwwwei
轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處缀皱,謝謝崎坊!

前言


??Spring的大名對(duì)于程序員來說如雷貫耳轮纫,IoC控制反轉(zhuǎn)作為Spring的核心,重要程度可想而知洪添,但是對(duì)于很多初學(xué)者而言看懂IoC確實(shí)不容易垦页,本文主要說清楚IoC到底是個(gè)什么東西,至于更深層的原理則需要讀者后續(xù)自己深究了干奢。

IoC


什么是IoC痊焊?

??我們先來看一下比較官方的解釋。
??IoC忿峻,Inversion of Control的縮寫薄啥,中文名稱為控制反轉(zhuǎn),意思是將對(duì)象的控制權(quán)轉(zhuǎn)移至第三方逛尚,例如IoC容器垄惧,即可由IoC容器來管理對(duì)象的生命周期、依賴關(guān)系等绰寞。
??相信你一定沒看懂到逊。

舉個(gè)例子

??在傳統(tǒng)的人員招聘模式中铣口,流程一般都是這樣:HR從多如海的應(yīng)聘簡歷中挑選然后進(jìn)行筆試、面試等等一系列篩選后發(fā)放offer觉壶。這一系列過程復(fù)雜而且費(fèi)時(shí)脑题,最關(guān)鍵的是結(jié)果還不理想,特別是針對(duì)某些特定的崗位很難通過這一模式物色到合適的人才資源铜靶。
??后來逐漸出現(xiàn)了一些公司專門提供類似的人才尋訪服務(wù)叔遂,這就是大名鼎鼎的獵頭行業(yè)。獵頭的興起可以說很大程度上改變了人才招聘的模式旷坦,現(xiàn)在公司需要招聘某個(gè)職位的人才掏熬,只需要告訴獵頭我要一個(gè)怎樣的人干怎樣的工作等等要求,獵頭就會(huì)通過自己的渠道去物色人才秒梅,經(jīng)過篩選后提供給客戶旗芬,大大簡化了招聘過程的繁瑣,提高了招聘的質(zhì)量和效率捆蜀。
??這其中一個(gè)很重要的變化就是公司HR將繁瑣的招聘尋訪人才的過程轉(zhuǎn)移至了第三方疮丛,也就是獵頭。相對(duì)比而言辆它,IoC在這里充當(dāng)了獵頭的角色誊薄,開發(fā)者即公司HR,而對(duì)象的控制權(quán)就相當(dāng)于人才尋訪過程中的一系列工作锰茉。

IoC設(shè)計(jì)模式和IoC容器

??回到我們所說的IoC呢蔫,首先我們需要肯定的是IoC并不是特指某種技術(shù),而是指一種思想或者說一種設(shè)計(jì)模式飒筑。我們可以簡單的理解為我們?cè)谶M(jìn)行程序業(yè)務(wù)邏輯的編程時(shí)通常需要大量的對(duì)象來協(xié)作完成片吊,而這些對(duì)象都需要我們通過類似如下語句

Object object=new Object();//對(duì)象申請(qǐng)

object.setName("XXX");//對(duì)象屬性初始化賦值

的方式申請(qǐng)和初始化,而這些就是所謂的對(duì)象的控制權(quán)协屡,IoC設(shè)計(jì)模式的目的就是把這些對(duì)象的控制權(quán)轉(zhuǎn)移至第三方俏脊,由第三方來進(jìn)行和管理類似對(duì)象申請(qǐng)、初始化肤晓、銷毀對(duì)象的控制權(quán)工作爷贫。
??對(duì)于開發(fā)者來說,對(duì)象的控制權(quán)的轉(zhuǎn)移意味著我們編程將更加簡便补憾,不用再去關(guān)心如何申請(qǐng)漫萄、初始化對(duì)象,甚至是管理對(duì)象盈匾、銷毀等復(fù)雜的過程腾务,這些都將由第三方完成,只需要告訴第三方我需要怎樣的對(duì)象使用即可威酒。
??這里還需要解釋一個(gè)概念窑睁,所謂的IoC容器挺峡,就是實(shí)現(xiàn)了IoC設(shè)計(jì)模式的框架

Spring IoC


??Spring IoC實(shí)現(xiàn)了IoC設(shè)計(jì)模式担钮,所以是IoC容器橱赠。所以,Spring IoC主要任務(wù)就是創(chuàng)建并且管理JavaBean的生命周期箫津,即之前提到的對(duì)象的控制權(quán)狭姨。
??那么對(duì)于Spring而言,JavaBean的生命周期包括哪些方面呢苏遥?這是我們下一個(gè)需要了解的問題饼拍。

Spring IoC的JavaBean的生命周期

(1)實(shí)例化JavaBean:Spring IoC容器實(shí)例化JavaBean
(2)初始化JavaBean:Spring IoC容器對(duì)JavaBean通過注入依賴進(jìn)行初始化
(3)使用JavaBean:基于Spring應(yīng)用對(duì)JavaBean實(shí)例的使用
(4)銷毀JavaBean:Spring IoC容器銷毀JavaBean實(shí)例

舉個(gè)例子

??我們來看一個(gè)Spring IoC的例子:
??編寫一個(gè)動(dòng)物接口,代碼如下:

package com.demo;

public interface Animal {
    void printWhoAmI();
}

??編寫一個(gè)老虎類實(shí)現(xiàn)動(dòng)物接口田炭,代碼如下:

package com.demo;

public class Tiger implements Animal {
    private String name;
    private int age;

    //省略屬性的set和get方法

    @Override
    public void printWhoAmI() {
        // TODO Auto-generated method stub
        System.out.println("I am " + name);
        System.out.println("I am " + age + " years old");
    }

}

??編寫Spring配置文件applicationContext.xml师抄,代碼如下:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans   
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="tiger" class="com.demo.Tiger">
        <property name="name">
            <value>Tom</value>
        </property>
        <property name="age">
            <value>3</value>
        </property>
    </bean>
</beans>

??編寫主測(cè)試類,代碼如下:

package com.demo;

public class Test {

    public static void main(String[] args) {
        Resource resource = new ClassPathResource("applicationContext.xml");
        BeanFactory beanFactory = new XmlBeanFactory(resource);
        Animal tiger = (Tiger) beanFactory.getBean("tiger"); // 獲取Tiger類對(duì)象tiger
        tiger.printWhoAmI();
    }

}

??我們可以發(fā)現(xiàn)Spring通過配置文件完成了Tiger類對(duì)象tiger申請(qǐng)和初始化教硫,我們?cè)谑褂肨iger類對(duì)象tiger時(shí)不再通過

// 創(chuàng)建Tiger類對(duì)象tiger
Animal tiger = new Tiger(); 
// Tiger類對(duì)象tiger初始化
tiger.setName("Tom"); 
tiger.setAge(3);

這種方式叨吮,而是將所有的JavaBean的生命周期操作和管理托管至Spring IoC容器,對(duì)于開發(fā)者而言瞬矩,我們只需要關(guān)心業(yè)務(wù)邏輯需要怎樣的JavaBean對(duì)象茶鉴,告訴容器,使用即可景用,這里再次體現(xiàn)了所謂的控制反轉(zhuǎn)的思想涵叮。

依賴注入

??我們可能會(huì)遇見這樣的情況,Spring IoC容器管理的對(duì)象中可能會(huì)依賴其他對(duì)象伞插,這是很常見的割粮。這就意味著Spring IoC的一個(gè)重點(diǎn)是在系統(tǒng)運(yùn)行中,動(dòng)態(tài)的向某個(gè)對(duì)象提供它所需要的其他對(duì)象蜂怎。這也是我們接下來要了解的依賴注入穆刻。
??接著上述例子我們來看一下依賴注入的情況:
??編寫一個(gè)籠子接口置尔,代碼如下:

package com.demo;

public interface Cage {
    void printInfo();
}

??編寫一個(gè)鐵籠子類實(shí)現(xiàn)籠子接口杠步,并且具有一個(gè)動(dòng)物類型的屬性,代碼如下:

package com.demo;

public class IronCage implements Cage {
    private String id;
    private Animal animal;

    //省略屬性的set和get方法

    @Override
    public void printInfo() {
        // TODO Auto-generated method stub
        System.out.println("I am a IronCage");
        System.out.println("My id is " + id);
        System.out.println("There is the animal information");
        animal.printWhoAmI();
    }

}

??將Spring配置文件applicationContext.xml改寫如下:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans   
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="tiger" class="com.demo.Tiger">
        <property name="name">
            <value>Tom</value>
        </property>
        <property name="age">
            <value>3</value>
        </property>
    </bean>
    <bean id="ironCage" class="com.demo.IronCage">
        <property name="id">
            <value>001</value>
        </property>
        <property name="animal">
            <ref bean="tiger" />
        </property>
    </bean>
</beans>

??IronCage類對(duì)象ironCage中依賴Animal類型的animal屬性榜轿,Spring IoC容器將Tiger類tiger對(duì)象注入作為animal的值幽歼,這就是依賴注入
??這里提一下谬盐,Spring支持多種屬性賦值的情況甸私,例如list、map:

<bean id="school" class="School">
    <!--1.value 普通賦值-->
    <property name="name">
        <value>XX學(xué)校</value>
    </property>
    <!--2.ref 引用其他JavaBean實(shí)例對(duì)象賦值-->
    <property name="student">
        <ref bean="student1" />
    </property>
    <!--3.list 集合類或者數(shù)組賦值-->
    <property name="studentList">
        <list>
            <ref bean="student1" />
            <value>student2</value>
        </list>
    </property>
    <!--4.map Map集合賦值-->
    <property name="studentMap">
        <map>
            <entry key="student1">
                <ref bean="student1" />
            </entry>
            <entry key="key2">
                <value>student2</value>
            </entry>
        </map>
    </property>
</bean>

Spring 如何實(shí)現(xiàn)IoC飞傀?

??了解了Spring IoC的強(qiáng)大功能之后皇型,我們可能都會(huì)好奇Spring究竟是如何做到這樣诬烹?
??Java一個(gè)很重要的特性就是支持反射(reflection)機(jī)制,它允許程序在運(yùn)行的時(shí)候動(dòng)態(tài)的生成對(duì)象弃鸦、執(zhí)行對(duì)象的方法绞吁、改變對(duì)象的屬性,Spring就是通過反射來實(shí)現(xiàn)注入的唬格。
??下面我們講講Spring實(shí)現(xiàn)這一過程的具體方式家破。這里我們需要介紹幾個(gè)重要的概念:
??(1) Bean的xml配置文件:我們以XML格式描述bean的相關(guān)信息,主要包括bean的名稱购岗、類型汰聋、屬性等等。
??(2) BeanDefinition :字面翻譯可以理解為bean的定義喊积,對(duì)于Spring來說我們之前使用的描述bean的XML配置文件并不能直接使用烹困,所以需要一個(gè)Spring能夠理解的數(shù)據(jù)結(jié)構(gòu)進(jìn)行存儲(chǔ)和管理這些bean的描述信息,這就是BeanDefinition乾吻。
??(3) BeanFactory:BeanFactory是用于訪問Spring Bean容器的根接口韭邓,是一個(gè)單純的Bean工廠,也就是常說的IOC容器的頂層定義溶弟,處于Spring的核心女淑。所以可以理解為Spring統(tǒng)一使用BeanFactory訪問Spring IoC容器,各種IOC容器是在其基礎(chǔ)上為了滿足不同需求而擴(kuò)展的辜御,包括經(jīng)常使用的ApplicationContext鸭你。
??通俗的說,如果我們將bean看做是一個(gè)產(chǎn)品擒权,那么Bean的xml配置文件可以看成是普通客戶對(duì)于想要產(chǎn)品的概念圖袱巨,而BeanDefinition則是專業(yè)人士根據(jù)客戶的概念圖設(shè)計(jì)產(chǎn)品的設(shè)計(jì)圖,對(duì)于BeanFactory碳抄,我們可以看成是一個(gè)能夠根據(jù)產(chǎn)品設(shè)計(jì)圖生產(chǎn)產(chǎn)品的工廠愉老。
??這樣來看,三者的關(guān)系是否更容易理解了呢剖效?接著我們繼續(xù)講Spring實(shí)現(xiàn)這一過程的具體方式嫉入。Spring IoC的實(shí)現(xiàn)過程主要分為兩部分,即IoC容器初始化和依賴注入璧尸。接著上面的比喻咒林,IoC容器初始化就是我們將客戶的產(chǎn)品概念圖轉(zhuǎn)換成產(chǎn)品設(shè)計(jì)圖,同時(shí)告知工廠的過程爷光;依賴注入工廠生產(chǎn)產(chǎn)品垫竞,我們通過工廠拿到我們所需的產(chǎn)品投入使用的過程。我們來看下詳細(xì)過程:

IoC容器初始化

IoC容器初始化過程

??IoC容器初始化過程主要經(jīng)過以下幾個(gè)階段:
??1.解析階段:Spring會(huì)解析Bean的XML配置文件蛀序,將XML元素進(jìn)行抽象欢瞪,抽象成Resource對(duì)象活烙。
??2.轉(zhuǎn)換階段:通過Resource對(duì)象將配置文件進(jìn)行抽象后轉(zhuǎn)換成Spring能夠理解的BeanDefinition結(jié)構(gòu)。
??3.注冊(cè)階段:Spring IoC容器的實(shí)現(xiàn)遣鼓,從根源上是beanfactory瓣颅,但真正可以作為一個(gè)可以獨(dú)立使用的ioc容器還是DefaultListableBeanFactory。
??DefaultListableBeanFactory間接實(shí)現(xiàn)了BeanFactory接口譬正,是整個(gè)bean加載的核心部分宫补,是Spring注冊(cè)及加載bean的默認(rèn)實(shí)現(xiàn),我們可以理解為Spring bean工廠的發(fā)動(dòng)機(jī)曾我。DefaultListableBeanFactory源碼中有2個(gè)重要的屬性粉怕,如下所示:

/** Map of bean definition objects, keyed by bean name */  
private final Map beanDefinitionMap = new ConcurrentHashMap(256);  
  
/** List of bean definition names, in registration order */  
private volatile List beanDefinitionNames = new ArrayList(256); 

??在bean的定義被解析轉(zhuǎn)換成BeanDefinition的過程中,同時(shí)解析得到beanName抒巢,將beanName和BeanDefinition存儲(chǔ)到beanDefinitionMap中贫贝,同時(shí)會(huì)將beanName存儲(chǔ)到beanDefinitionNames中。
??也就是說蛉谜,注冊(cè)的實(shí)質(zhì)就是以beanName為key稚晚,以beanDefinition為value,將其put到BeanFactory的HashMap屬性中型诚。

依賴注入

依賴注入過程

??依賴注入過程主要經(jīng)過以下幾個(gè)階段:
??1.bean初始化階段:完成IoC容器初始化后客燕,即上述第一過程后,Spring會(huì)加載沒有設(shè)置lazy-init(延遲加載)屬性的bean狰贯,進(jìn)行bean的初始化也搓。
??2.bean實(shí)例化階段:初始化bean,首先需要?jiǎng)?chuàng)建bean實(shí)例涵紊。
??3.bean屬性依賴注入階段依據(jù)BeanDefinition的信息來遞歸完成依賴注入傍妒。首先通過遞歸,在上下文查找需要的bean和構(gòu)造bean的遞歸調(diào)用摸柄;其次在依賴注入時(shí)颤练,通過遞歸調(diào)用容器的getBean()方法,得到當(dāng)前bean的依賴bean驱负,同時(shí)也觸發(fā)對(duì)依賴bean的創(chuàng)建和注入嗦玖。
??補(bǔ)充一下,DefaultListableBeanFactory間接繼承DefaultSingletonBeanRegistry电媳,DefaultSingletonBeanRegistry中有如下屬性踏揣,

/** Cache of singleton objects: bean name --> bean instance */  
private final Map singletonObjects = new ConcurrentHashMap(256);  

??singletonObjects用于存儲(chǔ)單例bean的實(shí)例庆亡,getBean()方法就是從這個(gè)Map里取實(shí)例對(duì)象匾乓。

最后對(duì)Spring IoC實(shí)現(xiàn)做個(gè)總結(jié)

??概括的描述一下,首先解析applicationgContext.xml又谋,將XML中定義的bean解析成Spring內(nèi)部的BeanDefinition拼缝,并以beanName為key娱局,BeanDefinition為value存儲(chǔ)到DefaultListableBeanFactory中的beanDefinitionMap(其實(shí)就是一個(gè)ConcurrentHashMap)中,同時(shí)將beanName存入beanDefinitionNames(List類型)中咧七,然后遍歷beanDefinitionNames中的beanName衰齐,進(jìn)行bean的實(shí)例化并填充屬性,在實(shí)例化的過程中继阻,如果有依賴沒有被實(shí)例化將先實(shí)例化其依賴耻涛,然后實(shí)例化本身,實(shí)例化完成后將實(shí)例存入單例bean的緩存中瘟檩,當(dāng)調(diào)用getBean方法時(shí)抹缕,到單例bean的緩存中查找,如果找到并經(jīng)過轉(zhuǎn)換后返回這個(gè)實(shí)例墨辛,之后就可以直接使用了卓研。
??這里強(qiáng)烈建議大家看一篇文章,對(duì)于Spring IoC的原理解釋的很清楚睹簇。
??spring ioc原理(看完后大家可以自己寫一個(gè)spring)
??同時(shí)感謝以下博文奏赘,寫作時(shí)作為參考借鑒
??Spring IOC核心源碼學(xué)習(xí)
??深入理解Spring系列

下節(jié) 長話短說Spring(2)之AOP面向切面編程
簡書 Wwwwei
轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處太惠,謝謝磨淌!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市凿渊,隨后出現(xiàn)的幾起案子伦糯,更是在濱河造成了極大的恐慌,老刑警劉巖嗽元,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敛纲,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡剂癌,警方通過查閱死者的電腦和手機(jī)淤翔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來佩谷,“玉大人旁壮,你說我怎么就攤上這事⌒程矗” “怎么了抡谐?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長桐猬。 經(jīng)常有香客問我麦撵,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任免胃,我火速辦了婚禮音五,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘羔沙。我一直安慰自己躺涝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布扼雏。 她就那樣靜靜地躺著坚嗜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪诗充。 梳的紋絲不亂的頭發(fā)上惶傻,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音其障,去河邊找鬼银室。 笑死,一個(gè)胖子當(dāng)著我的面吹牛励翼,可吹牛的內(nèi)容都是我干的蜈敢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼汽抚,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼抓狭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起造烁,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤否过,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后惭蟋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苗桂,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年告组,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了煤伟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡木缝,死狀恐怖便锨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情我碟,我是刑警寧澤放案,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站矫俺,受9級(jí)特大地震影響吱殉,放射性物質(zhì)發(fā)生泄漏掸冤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一考婴、第九天 我趴在偏房一處隱蔽的房頂上張望贩虾。 院中可真熱鬧催烘,春花似錦沥阱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舰始,卻和暖如春崇棠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丸卷。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工枕稀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人谜嫉。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓萎坷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親沐兰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哆档,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容