簡書 Wwwwei
轉(zhuǎn)載請注明原創(chuàng)出處,謝謝!
前言
??Spring的大名對于程序員來說如雷貫耳,IoC控制反轉(zhuǎn)作為Spring的核心,重要程度可想而知,但是對于很多初學(xué)者而言看懂IoC確實不容易,本文主要說清楚IoC到底是個什么東西,至于更深層的原理則需要讀者后續(xù)自己深究了。
IoC
什么是IoC?
??我們先來看一下比較官方的解釋。
??IoC,Inversion of Control的縮寫,中文名稱為控制反轉(zhuǎn),意思是將對象的控制權(quán)轉(zhuǎn)移至第三方,例如IoC容器,即可由IoC容器來管理對象的生命周期、依賴關(guān)系等。
??相信你一定沒看懂。
舉個例子
??在傳統(tǒng)的人員招聘模式中,流程一般都是這樣:HR從多如海的應(yīng)聘簡歷中挑選然后進行筆試、面試等等一系列篩選后發(fā)放offer。這一系列過程復(fù)雜而且費時,最關(guān)鍵的是結(jié)果還不理想,特別是針對某些特定的崗位很難通過這一模式物色到合適的人才資源。
??后來逐漸出現(xiàn)了一些公司專門提供類似的人才尋訪服務(wù),這就是大名鼎鼎的獵頭行業(yè)。獵頭的興起可以說很大程度上改變了人才招聘的模式,現(xiàn)在公司需要招聘某個職位的人才,只需要告訴獵頭我要一個怎樣的人干怎樣的工作等等要求,獵頭就會通過自己的渠道去物色人才,經(jīng)過篩選后提供給客戶,大大簡化了招聘過程的繁瑣,提高了招聘的質(zhì)量和效率。
??這其中一個很重要的變化就是公司HR將繁瑣的招聘尋訪人才的過程轉(zhuǎn)移至了第三方,也就是獵頭。相對比而言,IoC在這里充當(dāng)了獵頭的角色,開發(fā)者即公司HR,而對象的控制權(quán)就相當(dāng)于人才尋訪過程中的一系列工作。
IoC設(shè)計模式和IoC容器
??回到我們所說的IoC,首先我們需要肯定的是IoC并不是特指某種技術(shù),而是指一種思想或者說一種設(shè)計模式。我們可以簡單的理解為我們在進行程序業(yè)務(wù)邏輯的編程時通常需要大量的對象來協(xié)作完成,而這些對象都需要我們通過類似如下語句
Object object=new Object();//對象申請
object.setName("XXX");//對象屬性初始化賦值
的方式申請和初始化,而這些就是所謂的對象的控制權(quán),IoC設(shè)計模式的目的就是把這些對象的控制權(quán)轉(zhuǎn)移至第三方,由第三方來進行和管理類似對象申請、初始化、銷毀對象的控制權(quán)工作。
??對于開發(fā)者來說,對象的控制權(quán)的轉(zhuǎn)移意味著我們編程將更加簡便,不用再去關(guān)心如何申請、初始化對象,甚至是管理對象、銷毀等復(fù)雜的過程,這些都將由第三方完成,只需要告訴第三方我需要怎樣的對象使用即可。
??這里還需要解釋一個概念,所謂的IoC容器,就是實現(xiàn)了IoC設(shè)計模式的框架。
Spring IoC
??Spring IoC實現(xiàn)了IoC設(shè)計模式,所以是IoC容器。所以,Spring IoC主要任務(wù)就是創(chuàng)建并且管理JavaBean的生命周期,即之前提到的對象的控制權(quán)。
??那么對于Spring而言,JavaBean的生命周期包括哪些方面呢?這是我們下一個需要了解的問題。
Spring IoC的JavaBean的生命周期
(1)實例化JavaBean:Spring IoC容器實例化JavaBean
(2)初始化JavaBean:Spring IoC容器對JavaBean通過注入依賴進行初始化
(3)使用JavaBean:基于Spring應(yīng)用對JavaBean實例的使用
(4)銷毀JavaBean:Spring IoC容器銷毀JavaBean實例
舉個例子
??我們來看一個Spring IoC的例子:
??編寫一個動物接口,代碼如下:
package com.demo;
public interface Animal {
void printWhoAmI();
}
??編寫一個老虎類實現(xiàn)動物接口,代碼如下:
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>
??編寫主測試類,代碼如下:
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類對象tiger
tiger.printWhoAmI();
}
}
??我們可以發(fā)現(xiàn)Spring通過配置文件完成了Tiger類對象tiger申請和初始化,我們在使用Tiger類對象tiger時不再通過
// 創(chuàng)建Tiger類對象tiger
Animal tiger = new Tiger();
// Tiger類對象tiger初始化
tiger.setName("Tom");
tiger.setAge(3);
這種方式,而是將所有的JavaBean的生命周期操作和管理托管至Spring IoC容器,對于開發(fā)者而言,我們只需要關(guān)心業(yè)務(wù)邏輯需要怎樣的JavaBean對象,告訴容器,使用即可,這里再次體現(xiàn)了所謂的控制反轉(zhuǎn)的思想。
依賴注入
??我們可能會遇見這樣的情況,Spring IoC容器管理的對象中可能會依賴其他對象,這是很常見的。這就意味著Spring IoC的一個重點是在系統(tǒng)運行中,動態(tài)的向某個對象提供它所需要的其他對象。這也是我們接下來要了解的依賴注入。
??接著上述例子我們來看一下依賴注入的情況:
??編寫一個籠子接口,代碼如下:
package com.demo;
public interface Cage {
void printInfo();
}
??編寫一個鐵籠子類實現(xiàn)籠子接口,并且具有一個動物類型的屬性,代碼如下:
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類對象ironCage中依賴Animal類型的animal屬性,Spring IoC容器將Tiger類tiger對象注入作為animal的值,這就是依賴注入。
??這里提一下,Spring支持多種屬性賦值的情況,例如list、map:
<bean id="school" class="School">
<!--1.value 普通賦值-->
<property name="name">
<value>XX學(xué)校</value>
</property>
<!--2.ref 引用其他JavaBean實例對象賦值-->
<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 如何實現(xiàn)IoC?
??了解了Spring IoC的強大功能之后,我們可能都會好奇Spring究竟是如何做到這樣?
??Java一個很重要的特性就是支持反射(reflection)機制,它允許程序在運行的時候動態(tài)的生成對象、執(zhí)行對象的方法、改變對象的屬性,Spring就是通過反射來實現(xiàn)注入的。
??下面我們講講Spring實現(xiàn)這一過程的具體方式。這里我們需要介紹幾個重要的概念:
??(1) Bean的xml配置文件:我們以XML格式描述bean的相關(guān)信息,主要包括bean的名稱、類型、屬性等等。
??(2) BeanDefinition :字面翻譯可以理解為bean的定義,對于Spring來說我們之前使用的描述bean的XML配置文件并不能直接使用,所以需要一個Spring能夠理解的數(shù)據(jù)結(jié)構(gòu)進行存儲和管理這些bean的描述信息,這就是BeanDefinition。
??(3) BeanFactory:BeanFactory是用于訪問Spring Bean容器的根接口,是一個單純的Bean工廠,也就是常說的IOC容器的頂層定義,處于Spring的核心。所以可以理解為Spring統(tǒng)一使用BeanFactory訪問Spring IoC容器,各種IOC容器是在其基礎(chǔ)上為了滿足不同需求而擴展的,包括經(jīng)常使用的ApplicationContext。
??通俗的說,如果我們將bean看做是一個產(chǎn)品,那么Bean的xml配置文件可以看成是普通客戶對于想要產(chǎn)品的概念圖,而BeanDefinition則是專業(yè)人士根據(jù)客戶的概念圖設(shè)計產(chǎn)品的設(shè)計圖,對于BeanFactory,我們可以看成是一個能夠根據(jù)產(chǎn)品設(shè)計圖生產(chǎn)產(chǎn)品的工廠。
??這樣來看,三者的關(guān)系是否更容易理解了呢?接著我們繼續(xù)講Spring實現(xiàn)這一過程的具體方式。Spring IoC的實現(xiàn)過程主要分為兩部分,即IoC容器初始化和依賴注入。接著上面的比喻,IoC容器初始化就是我們將客戶的產(chǎn)品概念圖轉(zhuǎn)換成產(chǎn)品設(shè)計圖,同時告知工廠的過程;依賴注入是工廠生產(chǎn)產(chǎn)品,我們通過工廠拿到我們所需的產(chǎn)品投入使用的過程。我們來看下詳細(xì)過程:
IoC容器初始化

??IoC容器初始化過程主要經(jīng)過以下幾個階段:
??1.解析階段:Spring會解析Bean的XML配置文件,將XML元素進行抽象,抽象成Resource對象。
??2.轉(zhuǎn)換階段:通過Resource對象將配置文件進行抽象后轉(zhuǎn)換成Spring能夠理解的BeanDefinition結(jié)構(gòu)。
??3.注冊階段:Spring IoC容器的實現(xiàn),從根源上是beanfactory,但真正可以作為一個可以獨立使用的ioc容器還是DefaultListableBeanFactory。
??DefaultListableBeanFactory間接實現(xiàn)了BeanFactory接口,是整個bean加載的核心部分,是Spring注冊及加載bean的默認(rèn)實現(xiàn),我們可以理解為Spring bean工廠的發(fā)動機。DefaultListableBeanFactory源碼中有2個重要的屬性,如下所示:
/** 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的過程中,同時解析得到beanName,將beanName和BeanDefinition存儲到beanDefinitionMap中,同時會將beanName存儲到beanDefinitionNames中。
??也就是說,注冊的實質(zhì)就是以beanName為key,以beanDefinition為value,將其put到BeanFactory的HashMap屬性中。
依賴注入

??依賴注入過程主要經(jīng)過以下幾個階段:
??1.bean初始化階段:完成IoC容器初始化后,即上述第一過程后,Spring會加載沒有設(shè)置lazy-init(延遲加載)屬性的bean,進行bean的初始化。
??2.bean實例化階段:初始化bean,首先需要創(chuàng)建bean實例。
??3.bean屬性依賴注入階段:依據(jù)BeanDefinition的信息來遞歸完成依賴注入。首先通過遞歸,在上下文查找需要的bean和構(gòu)造bean的遞歸調(diào)用;其次在依賴注入時,通過遞歸調(diào)用容器的getBean()方法,得到當(dāng)前bean的依賴bean,同時也觸發(fā)對依賴bean的創(chuàng)建和注入。
??補充一下,DefaultListableBeanFactory間接繼承DefaultSingletonBeanRegistry,DefaultSingletonBeanRegistry中有如下屬性,
/** Cache of singleton objects: bean name --> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);
??singletonObjects用于存儲單例bean的實例,getBean()方法就是從這個Map里取實例對象。
最后對Spring IoC實現(xiàn)做個總結(jié)
??概括的描述一下,首先解析applicationgContext.xml,將XML中定義的bean解析成Spring內(nèi)部的BeanDefinition,并以beanName為key,BeanDefinition為value存儲到DefaultListableBeanFactory中的beanDefinitionMap(其實就是一個ConcurrentHashMap)中,同時將beanName存入beanDefinitionNames(List類型)中,然后遍歷beanDefinitionNames中的beanName,進行bean的實例化并填充屬性,在實例化的過程中,如果有依賴沒有被實例化將先實例化其依賴,然后實例化本身,實例化完成后將實例存入單例bean的緩存中,當(dāng)調(diào)用getBean方法時,到單例bean的緩存中查找,如果找到并經(jīng)過轉(zhuǎn)換后返回這個實例,之后就可以直接使用了。
??這里強烈建議大家看一篇文章,對于Spring IoC的原理解釋的很清楚。
??spring ioc原理(看完后大家可以自己寫一個spring)
??同時感謝以下博文,寫作時作為參考借鑒。
??Spring IOC核心源碼學(xué)習(xí)
??深入理解Spring系列
下節(jié) 長話短說Spring(2)之AOP面向切面編程
簡書 Wwwwei
轉(zhuǎn)載請注明原創(chuàng)出處,謝謝!