簡書 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容器初始化過程主要經(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)出處太惠,謝謝磨淌!