本文適合初學(xué)者,也適合老手鞏固以前的知識(shí),針對(duì)IOC的知識(shí)點(diǎn)進(jìn)行詳細(xì)的講解。
在剛開始接觸IOC的時(shí)候,很多初學(xué)者回聽到很多名詞 IOC、DI、Spring IOC 等,往往聽的一頭霧水。所以在學(xué)習(xí)之前我們先梳理下這些概念,之后再去深入學(xué)習(xí) IoC 的實(shí)現(xiàn)原理部分。
很多人都知道 IOC,都知道 IOC 是 Spring 容器的內(nèi)核。
AOP、申明式事務(wù)等功能在此基礎(chǔ)上開發(fā)的。甚至在一提到 IOC 這個(gè)概念的時(shí)候就很自然的想到 Spring 容器,并將它們混為一談,認(rèn)為 IoC 就是 Spring IoC。這樣理解雖然并不妨礙我們使用 Spring 框架,但是兩者卻不是一回事。
IOC的概念和思想
IOC (Inversion Of Control 譯為 反轉(zhuǎn)控制),顧名思義,其一是控制,其二是反轉(zhuǎn)。即某一個(gè)接口具體實(shí)現(xiàn)類的選擇控制權(quán)從調(diào)用類中移除,轉(zhuǎn)交給第三方?jīng)Q定,在 Spring 容器中是由 Bean 配置來進(jìn)行控制的。
這段話比較繞口,簡單來說就是有了 IoC 之后你需要什么對(duì)象,IoC 容器幫你解決,你在家等著,IoC 容器主動(dòng)送貨上門。沒有 IoC 之前是自己親手去創(chuàng)建管理對(duì)象,現(xiàn)在直接現(xiàn)成的送過來。
再回頭看看,IoC 的 控制 和 反轉(zhuǎn) 這個(gè)兩個(gè)名詞就很好理解了:
再未使用 IoC 之前,需要使用一個(gè)對(duì)象需要直接 new 一個(gè)。那么你所依賴的對(duì)象就需要你自己去控制。但是有了 IoC 之后,直接由 IoC 容器來控制依賴的對(duì)象,當(dāng)你依賴一個(gè)對(duì)象的時(shí)候就由 IoC 容器創(chuàng)建后注入到被注入的對(duì)象中。
- 控制表示對(duì)對(duì)象的控制
- 反轉(zhuǎn)表示依賴對(duì)象的獲取反轉(zhuǎn)了
而實(shí)現(xiàn) IOC 概念的容器除了 Spring 還有 Guice,Jboss 等。
所以 IOC 和 Spring 中的 IOC 不能混為一談??梢哉f,IOC 本身只是一種概念和設(shè)計(jì)思想,Spring 容器實(shí)現(xiàn)了 IOC 思想,是 IOC 的實(shí)踐。
但由于 IOC 這種命名確實(shí)不夠開門見山,因此業(yè)界曾進(jìn)行了廣泛的討論,最終軟件界的泰斗級(jí)人物 Martin Fowler 提出了 DI(Dependency Injection,依賴注入)的概念用來替代 IoC。即讓調(diào)用類對(duì)某一接口實(shí)現(xiàn)類的依賴關(guān)系由第三方(容器或協(xié)作類)注入,以移除掉用類對(duì)某一接口實(shí)現(xiàn)類的依賴。
可以看出 DI 依賴注入的解釋 比 IoC 反轉(zhuǎn)控制 更直接明了,初學(xué)者更容易理解。
很多人將 IoC 和 DI 混為一談,這樣雖然也不影響使用,但至少要知道它由 IoC 變?yōu)?DI 的發(fā)展過程。
Spring IoC 的原理
Spring IoC 的底層實(shí)現(xiàn)是基于反射技術(shù),不了解這塊知識(shí)的同學(xué)可以看下這兩篇文章學(xué)習(xí)一下:《反射一開,誰都不愛》,《工廠模式的三種實(shí)現(xiàn),就這么簡單!》。
前面說了 IoC 容器是一個(gè)大的工廠來管理所有對(duì)象和它們的依賴關(guān)系,Spring 再處理這些對(duì)象和依賴關(guān)系也很簡單:
- 使用反射技術(shù)獲取對(duì)象的信息包括:類信息、成員、方法等等
- 再通過 xml 配置 或者 注解 的方式,說明依賴關(guān)系
- 在調(diào)用類需要使用其他類的時(shí)候,不再通過調(diào)用類自己實(shí)現(xiàn),而是通過 IoC 容器進(jìn)行注入。
那 Spring 究竟是如何知道哪些對(duì)象是需要管理的呢?如何進(jìn)行管理的呢?又是如何進(jìn)行注入的呢?
Spring 通過一個(gè)配置文件或注解來描述 Bean 和 Bean 之間的依賴關(guān)系,根據(jù) Bean 配置信息在容器內(nèi)部創(chuàng)建Bean定義注冊(cè)表,根據(jù)注冊(cè)表加載、實(shí)例化 Bean、建立Bean與Bean之間的依賴關(guān)系,還提供了 Bean 實(shí)例緩存、生命周期管理、Bean 實(shí)例代理、事件發(fā)布、資源裝載等高級(jí)服務(wù)。
Spring 框架和核心接口是 Bean 工廠,工廠分為兩種:
- BeanFactory:這是最基礎(chǔ)的,面向 Spring 的
- ApplicationContext:是面向 Spring 框架開發(fā)者的,幾乎所有的應(yīng)用場合都可以使用它。
BeanFactory 有著龐大的繼承、實(shí)現(xiàn)體系,有眾多的子接口、實(shí)現(xiàn)類。
來看一下 BeanFactory 的基本類體系:

ApplicationContext 的繼承體系:

BeanFactory 負(fù)責(zé)讀取bean配置文檔,管理bean的加載,實(shí)例化,維護(hù)bean之間的依賴關(guān)系,負(fù)責(zé)bean的聲明周期。
-
ApplicationContext 除了提供上述 BeanFactory 所能提供的功能之外,還提供了更完整的框架功能:
- 國際化支持
- 資源訪問:
Resource rs = ctx. getResource(“classpath:config.properties”), “file:c:/config.properties” - 事件傳遞:通過實(shí)現(xiàn)ApplicationContextAware接口
介紹了 Bean 的工廠類,再來詳細(xì)的解答下 Spring 是如何管理這些類的?這個(gè)問題。
Bean的生命周期
Bean 的生命周期可以說是面試的時(shí)候常問的問題之一,我們可以用下面兩張圖直觀的感受下:
BeanFactory 中 Bean 的生命周期:

ApplicationContext 中 Bean 的生命周期:

可以看到在進(jìn)行實(shí)例化、設(shè)置屬性值、通過 init-method 屬性配置的初始化方法、放入 Spring 緩沖池 前后都有方法可以對(duì) Bean 進(jìn)行再次加工。
Bean 的生命周期從開始到最終銷毀其中經(jīng)歷的方法可以分為四類:
- Bean自身的方法:如調(diào)用 Bean 構(gòu)造函數(shù)實(shí)例化 Bean,調(diào)用 Setter 設(shè)置 Bean 的屬性值以及通過的 init-method 和 destroy-method 所指定的方法;
- Bean級(jí)生命周期接口方法:如 BeanNameAware、 BeanFactoryAware、 InitializingBean 和 DisposableBean,這些接口方法由 Bean 類直接實(shí)現(xiàn);
- 容器級(jí)生命周期接口方法: InstantiationAwareBean PostProcessor 和 BeanPostProcessor 這兩個(gè)接口實(shí)現(xiàn),一般稱它們的實(shí)現(xiàn)類為“ 后處理器” 。這些后處理器的影響是全局性的。用戶可以通過合理地編寫后處理器,讓其僅對(duì)感興趣Bean 進(jìn)行加工處理。
- 工廠后處理器接口方法
Bean 的裝配過程
簡單敘述下 Bean 的裝配過程:
- BeanDefinitionReader 讀取 Resource 所指向的配置文件資源,然后解析配置文件。配置文件中每一個(gè)解析成一個(gè) BeanDefinition 對(duì)象,并保存到 BeanDefinitionRegistry 中;
- 容器掃描 BeanDefinitionRegistry 中的BeanDefinition;調(diào)用InstantiationStrategy 進(jìn)行Bean實(shí)例化的工作;使用 BeanWrapper 完成Bean屬性的設(shè)置工作;
- 單例Bean緩存池:Spring 在DefaultSingletonBeanRegistry類中提供了一個(gè)用于緩存單實(shí)例 Bean 的緩存器,它是一個(gè)用HashMap實(shí)現(xiàn)的緩存器,單實(shí)例的 Bean 以beanName為鍵保存在這個(gè)HashMap中。
ApplicationContext 和 BeanFactory 的區(qū)別
區(qū)別:
- BeanFactory 在啟動(dòng)的時(shí)候不會(huì)去實(shí)例化 Bean,中有從容器中拿 Bean 的時(shí)候才會(huì)去實(shí)例化,ApplicationContext 在啟動(dòng)的時(shí)候就把所有的 Bean 全部實(shí)例化了。它還可以為 Bean 配置 lazy-init=true 來讓 Bean 延遲實(shí)例化;
- BeanFacotry 是 spring 中比較原始的 Factory,無法支持 spring 的許多插件,如 AOP 功能、Web 應(yīng)用等。 ApplicationContext接口
- MessageSource, 提供國際化的消息訪問
- 資源訪問,如URL和文件
- 事件傳播
- 載入多個(gè)(有繼承關(guān)系)上下文 ,使得每一個(gè)上下文都專注于一個(gè)特定的層次,比如應(yīng)用的web層
Bean的裝配方式
Bean 的裝配方式有四種:
- 基于 xml 的配置
- 注解方式
- java類的配置
- 基于Groovy DSL的配置(不太常見)
一般都是用 xml 和 注解 兩種配置,其他的方式使用的不多。這里不多做敘述
依賴注入的方式
依賴注入的方式有三種方式:
- 屬性注入:使用setter方法
- 構(gòu)造函數(shù)注入
- 工廠方法注入
這其中屬性注入的方式比較常用。
對(duì)象之間關(guān)系
對(duì)象之間有三種關(guān)系:
- 依賴:挺少用的(在 xml 中使用depends-on就是依賴關(guān)系了-->前置依賴【依賴的Bean需要初始化之后,當(dāng)前Bean才會(huì)初始化】)
- 繼承:
- 引用:最常見(使用ref就是引用關(guān)系了)
Bean的作用域
- singleton:單例,只有一個(gè)對(duì)象
- prototype:多例,每次都會(huì)創(chuàng)建一個(gè)新對(duì)象
- request:每次請(qǐng)求都會(huì)參數(shù)一個(gè)對(duì)象
- session:每個(gè)session都會(huì)產(chǎn)生一個(gè)對(duì)象
- global session:所有的session都用一個(gè)對(duì)象
默認(rèn)Bean的作用域是單例的,其他需要使用不同類型的作用域,需要單獨(dú)配置。
這里有個(gè)知識(shí)點(diǎn) @lookup注解需要注意。如果我們需要在 singleton 的 Bean 中注入一個(gè) prototype 的 Bean 并希望在每次調(diào)用 singleton 的 Bean 的時(shí)候獲取的都是 prototype 的 Bean ?這就需要使用 @lookup注解。
IOC 的優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):實(shí)現(xiàn)組件之間的解耦,提高程序的靈活性和可維護(hù)性。
- 缺點(diǎn):生成一個(gè)對(duì)象的步驟變復(fù)雜了,生成因?yàn)槭鞘褂梅瓷渚幊蹋谛噬嫌行p耗。但相對(duì)于IoC提高的維護(hù)性和靈活性來說,這點(diǎn)損耗是微不足道的,除非某對(duì)象的生成對(duì)效率要求特別高。
總結(jié)
這里,再次做一個(gè)知識(shí)點(diǎn)的總結(jié):
IoC 是一種概念和設(shè)計(jì)思想,Guice,Spring,Jboss都實(shí)現(xiàn)了這種理念,Spring將其發(fā)揚(yáng)光大
Spring IoC 容器實(shí)現(xiàn)基于反射技術(shù)
IoC 的3種類型,Spring IOC 支持2種,構(gòu)造函數(shù)注入和屬性注入
Spring 容器工廠分為兩種:ApplicationContext、BeanFactory。異同點(diǎn):1. BeanFactory 在啟動(dòng)的時(shí)候不會(huì)去實(shí)例化 Bean,ApplicationContext 在啟動(dòng)的時(shí)候會(huì)。2. BeanFacotry 是 spring 中比較原始的 Factory,無法支持 spring 的許多插件,如 AOP 功能、Web 應(yīng)用等。 ApplicationContext 支持。
-
Bean的生命周期的過程:
- 四類方法:
- Bean自身的方法:如Setter init-method 和 destroy-method 所指定的方法;
- Bean級(jí)生命周期接口方法:如 BeanNameAware、 BeanFactoryAware、 InitializingBean 和 DisposableBean,這些接口方法由 Bean 類直接實(shí)現(xiàn);
- 容器級(jí)生命周期接口方法: InstantiationAwareBean PostProcessor 和 BeanPostProcessor 這兩個(gè)接口實(shí)現(xiàn),一般稱它們的實(shí)現(xiàn)類為“ 后處理器” 。這些后處理器的影響是全局性的。用戶可以通過合理地編寫后處理器,讓其僅對(duì)感興趣Bean 進(jìn)行加工處理。
- 工廠后處理器接口方法
- 四類方法:
-
Bean 的裝配過程:
- BeanDefinitionReader 讀取 Resource 所指向的配置文件資源,然后解析配置文件。配置文件中每一個(gè)解析成一個(gè) BeanDefinition 對(duì)象,并保存到 BeanDefinitionRegistry 中;
- 容器掃描 BeanDefinitionRegistry 中的BeanDefinition;調(diào)用InstantiationStrategy 進(jìn)行Bean實(shí)例化的工作;使用 BeanWrapper 完成Bean屬性的設(shè)置工作;
- 單例Bean緩存池:Spring 在DefaultSingletonBeanRegistry類中提供了一個(gè)用于緩存單實(shí)例 Bean 的緩存器,它是一個(gè)用HashMap實(shí)現(xiàn)的緩存器,單實(shí)例的 Bean 以beanName為鍵保存在這個(gè)HashMap中。
bean的裝配方式,四種:1. xml;2. 注解;3. java類的配置;4. 基于Groovy DSL的配置(不太常見)
依賴注入的方式,三種:1. 屬性注入:通過setXxx()方法注入Bean的屬性值;2. 構(gòu)造器注入:通過構(gòu)造函數(shù)注入;3. 工廠方法注入:用靜態(tài)工廠或非靜態(tài)工廠方法注入
bean的五個(gè)作用域:1. singleton:單例,只有一個(gè)對(duì)象;2. prototype:多例,每次都會(huì)創(chuàng)建一個(gè)新對(duì)象;3. request:每次請(qǐng)求都會(huì)參數(shù)一個(gè)對(duì)象;4. session:每個(gè)session都會(huì)產(chǎn)生一個(gè)對(duì)象;5. global session:所有的session都用一個(gè)對(duì)象
高級(jí)主題:1. 國際化:多語言;2. 容器事件:監(jiān)聽對(duì)應(yīng)的事件并做出反應(yīng),比如:RequestHandledEvent,當(dāng)一個(gè)Http請(qǐng)求被處理后產(chǎn)生事件;3. 引用外部屬性文件:數(shù)據(jù)庫的用戶名密碼信息存儲(chǔ)在properties文件中,可以使用${XX} 進(jìn)行引用;4. 屬性編輯器:轉(zhuǎn)化為基本類型的利器
最后再附上Spring-Ioc的思維導(dǎo)圖,建議點(diǎn)贊、關(guān)注、收藏,以便隨時(shí)復(fù)習(xí)鞏固知識(shí):

我是帥帥,一個(gè)在互聯(lián)網(wǎng)茍且偷生的工具人。