控制反轉(IoC)是一個比較抽象的概念,它主要用來消減計算機程序的耦合問題,是Spring框架的核心。
依賴注入(DI)是IoC的另一種說法,只是從不一樣的角度描述相同的概念。
看完這兩句,是否是不但沒懂,反而更迷惑了,別急,往下看:css
咱們都知道,在採用面向對象方法設計的軟件系統中,它的底層實現都是由N個對象組成的,全部的對象經過彼此的合做,最終實現系統的業務邏輯。html
若是咱們打開機械式手錶的後蓋,就會看到與上面圖片相似的情形,各個齒輪分別帶動時針、分針和秒針順時針旋轉,從而在錶盤上產生正確的時間。上圖中描述的就是這樣的一個齒輪組,它擁有多個獨立的齒輪,這些齒輪相互齧合在一塊兒,協同工做,共同完成某項任務。咱們能夠看到,在這樣的齒輪組中,若是有一個齒輪出了問題,就可能會影響到整個齒輪組的正常運轉。web
齒輪組中齒輪之間的齧合關係,與軟件系統中對象之間的耦合關係很是類似。對象之間的耦合關係是沒法避免的,也是必要的,這是協同工做的基礎。如今,伴隨着工業級應用的規模愈來愈龐大,對象之間的依賴關係也愈來愈複雜,常常會出現對象之間的多重依賴性關係,所以,架構師和設計師對於系統的分析和設計,將面臨更大的挑戰。對象之間耦合度太高的系統,必然會出現牽一髮而動全身的情形。spring
耦合關係不只會出如今對象與對象之間,也會出如今軟件系統的各模塊之間,以及軟件系統和硬件系統之間。如何下降系統之間、模塊之間和對象之間的耦合度,是軟件工程永遠追求的目標之一。爲了解決對象之間的耦合度太高的問題,軟件專家Michael Mattson提出了IoC理論,用來實現對象之間的「解耦」,目前這個理論已經被成功地應用到實踐當中,不少的J2EE項目均採用了IoC框架產品Spring。編程
IoC是Inversion of Control的縮寫,多數書籍翻譯成「控制反轉」,還有些書籍翻譯成爲「控制反向」或者「控制倒置」。
簡單來講就是把複雜系統分解成相互合做的對象,這些對象類經過封裝之後,內部實現對外部是透明的,從而下降了解決問題的複雜度,並且能夠靈活地被重用和擴展。IoC理論提出的觀點大致是這樣的:藉助於「第三方」實現具備依賴關係的對象之間的解耦,以下圖:api
你們看到了吧,因爲引進了中間位置的「第三方」,也就是IOC容器,使得A、B、C、D這4個對象沒有了耦合關係,齒輪之間的傳動所有依靠「第三方」了,所有對象的控制權所有上繳給「第三方」IOC容器,因此,IOC容器成了整個系統的關鍵核心,它起到了一種相似「粘合劑」的做用,把系統中的全部對象粘合在一塊兒發揮做用,若是沒有這個「粘合劑」,對象與對象之間會彼此失去聯繫,這就是有人把IOC容器比喻成「粘合劑」的由來。服務器
咱們再來作個試驗:把上圖中間的IOC容器拿掉,而後再來看看這套系統:架構
咱們如今看到的畫面,就是咱們要實現整個系統所須要完成的所有內容。這時候,A、B、C、D這4個對象之間已經沒有了耦合關係,彼此毫無聯繫,這樣的話,當你在實現A的時候,根本無須再去考慮B、C和D了,對象之間的依賴關係已經下降到了最低程度。因此,若是真能實現IOC容器,對於系統開發而言,這將是一件多麼美好的事情,參與開發的每一成員只要實現本身的類就能夠了,跟別人沒有任何關係!app
咱們再來看看,控制反轉(IOC)到底爲何要起這麼個名字?咱們來對比一下:
軟件系統在沒有引入IOC容器以前,如圖1所示,對象A依賴於對象B,那麼對象A在初始化或者運行到某一點的時候,本身必須主動去建立對象B或者使用已經建立的對象B。不管是建立仍是使用對象B,控制權都在本身手上。框架
軟件系統在引入IOC容器以後,這種情形就徹底改變了,因爲IOC容器的加入,對象A與對象B之間失去了直接聯繫,因此,當對象A運行到須要對象B的時候,IOC容器會主動建立一個對象B注入到對象A須要的地方。
經過先後的對比,咱們不難看出來:對象A得到依賴對象B的過程,由主動行爲變爲了被動行爲,控制權顛倒過來了,這就是「控制反轉」這個名稱的由來。
方便理解,我舉個生活的例子:
咱們都學了面向對象的編程思想,在生活中,當人們須要一件東西時,第一反應就是找東西,例如想吃麪包,如今有兩種狀況,第一種是沒有面包店,第二種是有面包店。
第一種狀況就是咱們以前一直遇到的狀況,在沒有面包店的狀況下,最直觀的作法可能就是你按照本身的口味製做麪包,也就是一個麪包須要主動製做,誰想吃了就本身New。而我主要說的是第二種狀況,就是有面包店,你想吃麪包的時候找到麪包店,把本身的口味告訴店家,店家就能夠給你作符合你口味的麪包了。注意:你並無製做麪包,而是由店家制做,可是徹底符合你的口味。
這是一個很生活的例子,你們都明白,但這裏包含了Spring中很重要的思想——控制反轉,就是把製做麪包的主動權交給店家,麪包就是對象,店家至關於一個大容器,你想要什麼對象,就讓大容器去給你生產,這就是控制反轉思想。
再詳細點,當某個Java對象(調用者,例如你)須要調用另外一個Java對象(被調用者,即被依賴對象,例如麪包)時,在傳統編程模式下,調用者一般會採用「New 被調用者」的代碼方式來建立對象(例如你本身製做麪包)。這種方式會增長調用者與被調用者之間的耦合性,不利於後期代碼的升級和維護。
當Spring框架出現後,對象的實例再也不由調用者來建立,而是由 Spring容器(例如麪包店)來建立。Spring容器會負責控制程序之間的關係(例如麪包店負責控制你與麪包的關係),而不是由調用者的程序代碼直接控制。這樣,控制權由調用者轉移到Spring容器,控制權發生了反轉,這就是Spring的控制反轉。
2004年,Martin Fowler探討了同一個問題,既然IOC是控制反轉,那麼究竟是「哪些方面的控制被反轉了呢?」,通過詳細地分析和論證後,他得出了答案:「得到依賴對象的過程被反轉了」。控制被反轉以後,得到依賴對象的過程由自身管理變爲了由IOC容器主動注入。因而,他給「控制反轉」取了一個更合適的名字叫作「依賴注入)」。他的這個答案,實際上給出了實現IOC的方法:注入。
所謂依賴注入,就是由IOC容器在運行期間,動態地將某種依賴關係注入到對象之中。
因此,依賴注入(DI)和控制反轉(IOC)是從不一樣的角度的描述的同一件事情,就是指經過引入IOC容器,利用依賴關係注入的方式,實現對象之間的解耦。
我再舉一個生活中的例子,來幫助理解依賴注入的過程。你們對USB接口和USB設備應該都很熟悉吧,USB爲咱們使用電腦提供了很大的方便,如今有不少的外部設備都支持USB接口。
如今,咱們利用電腦主機和USB接口來實現一個任務:從外部USB設備讀取一個文件。
電腦主機讀取文件的時候,它一點也不會關心USB接口上鍊接的是什麼外部設備,並且它確實也無須知道。它的任務就是讀取USB接口,掛接的外部設備只要符合USB接口標準便可。因此,若是我給電腦主機鏈接上一個U盤,那麼主機就從U盤上讀取文件;若是我給電腦主機鏈接上一個外置硬盤,那麼電腦主機就從外置硬盤上讀取文件。掛接外部設備的權力由我做主,即控制權歸我,至於USB接口掛接的是什麼設備,電腦主機是決定不了,它只能被動的接受。電腦主機須要外部設備的時候,根本不用它告訴我,我就會主動幫它掛上它想要的外部設備,你看個人服務是多麼的到位。這就是咱們生活中常見的一個依賴注入的例子。在這個過程當中,我就起到了IOC容器的做用。
經過這個例子,依賴注入的思路已經很是清楚:當電腦主機讀取文件的時候,我就把它所要依賴的外部設備,幫他掛接上。整個外部設備註入的過程和一個被依賴的對象在系統運行時被注入另一個對象內部的過程徹底同樣。
咱們把依賴注入應用到軟件系統中,再來描述一下這個過程:
對象A依賴於對象B,當對象 A須要用到對象B的時候,IOC容器就會當即建立一個對象B送給對象A。IOC容器就是一個對象製造工廠,你須要什麼,它會給你送去,你直接使用就好了,而不再用去關心你所用的東西是如何製成的,也不用關心最後是怎麼被銷燬的,這一切所有由IOC容器包辦。
在傳統的實現中,由程序內部代碼來控制組件之間的關係。咱們常用new關鍵字來實現兩個組件之間關係的組合,這種實現方式會形成組件之間耦合。IOC很好地解決了該問題,它將實現組件間關係從程序內部提到外部容器,也就是說由容器在運行期將組件間的某種依賴關係動態注入組件中。
在以前,咱們須要用構造方法或者set()方法給一些成員變量賦值,從Spring容器角度來看,Spring容器負責將被依賴對象賦值給調用者的成員變量,至關於爲調用者注入它所依賴的實例,容器能知道哪一個組件(類)運行的時候,須要另一個類(組件),容器經過反射的形式,將容器中準備好的對象注入(利用反射給屬性賦值)到另外一個類中,這就是Spring的依賴注入。
綜上所述,控制反轉是一種經過描述(在Spring中能夠是XML或註解)並經過第三方去產生或獲取特定對象的方式。在Spring中實現控制反轉的是IoC容器,其實現方法是依賴注入。
IoC中最基本的技術就是「反射」,有關反射的概念和用法,你們應該都很清楚,通俗來說就是根據給出的類名(字符串方式)來動態地生成對象。這種編程方式可讓對象在生成時才決定究竟是哪種對象。反射的應用是很普遍的,不少的成熟的框架,好比象Java中的Hibernate、Spring框架,都是把「反射」作爲最基本的技術手段。
咱們能夠把IoC容器的工做模式看作是工廠模式的昇華,能夠把IoC容器看做是一個工廠,這個工廠裏要生產的對象都在配置文件中給出定義,而後利用反射,根據配置文件中給出的類名生成相應的對象。從實現來看,IoC是把之前在工廠方法裏寫死的對象生成代碼,改變爲由配置文件來定義,也就是把工廠和對象生成這二者獨立分隔開來,目的就是提升靈活性和可維護性。
之前都是本身new對象,如今全部的對象交給容器建立。Spring IoC容器的設計主要是基於BeanFactory和Application兩個接口。
BeanFactory由org.springframework.beans.factory.BeanFactory接口定義,它提供了完整的IoC服務支持,是一個管理Bean的工廠,主要負責初始化各類Bean。
BeanFactory接口有多個實現類,其中比較經常使用的是org.springframework.beans.factory.xml.XmlBeanFactory,該類會根據XML配置文件中的定義來裝配Bean(有關Bean的知識我在後面的文章中會講)。
建立項目及導入Maven模塊過程看《使用IDEA開發Spring入門程序》,在這就不贅述了。在這繼續前面的項目,按照下面的步驟補充:
package entity; public class Person { private String name; private String sex; public Person() { System.out.println("無參構造調用了..."); } public Person(String name, String sex) { this.name = name; this.sex = sex; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--將指定類TestDaoImpl配置給Spring,即註冊一個TestDaoImpl對象,讓Spring建立其實例--> <!-- 一個Bean標籤能夠註冊一個組件(對象、類) class:寫要註冊的組件的全類名 id:這個對象的惟一標識 --> <bean id="test" class="dao.TestDaoImpl"/> <bean id="person1" class="entity.Person"/> </beans>
在建立BeanFactory實例時須要提供XML文件的絕對路徑。
package test; import dao.TestDao; import entity.Person; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.FileSystemResource; public class TestDemo { @Test public void test1(){ //初始化spring容器,加載配置文件 BeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource("D:\\MyNewWorld\\Study_JAVA\\newspringdemo\\src\\main\\resource\\applicationContext.xml")); Person person1 =(Person) beanFactory.getBean("person1"); person1.setName("光頭強"); person1.setSex("男"); System.out.println(person1.getName()+"是"+person1.getSex()+"人!!!"); } }
測試成功,使用BeanFactory實例加載Spring配置文件在實際開發中並很少見,讀者瞭解便可。
ApplicationContext是BeanFactory的子接口,也稱爲應用上下文,由org.springframework.context.ApplicationContext接口定義。
ApplicationContext接口除了包含BeanFactory的全部功能之外,還添加了對國際化、資源訪問、事件傳播等內容的支持。
建立ApplicationContext接口實例一般有如下三種方法:
ClassPathXmlApplicationContext將從類路徑目錄(src根目錄)中尋找指定的XML配置文件,首先咱們考慮一個問題:Person對象是何時建立好的?
爲了方便查看,我在Person類的無參構造函數中加上以下圖所示的語句:
先看下面這段代碼:
package test; import dao.TestDao; import entity.Person; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.FileSystemResource; public class TestDemo { @Test public void test2(){ //初始化spring容器ApplicationContext,加載配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); }
}
看下圖中的運行結果,咱們不難知道容器中對象的建立在容器建立完成的時候就已經建立好了。
好了,我把代碼補充完整吧:
package test; import dao.TestDao; import entity.Person; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.FileSystemResource; public class TestDemo { @Test public void test2(){ //初始化spring容器ApplicationContext,加載配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //經過容器獲取test實例 Person person1 =(Person) applicationContext.getBean("person1"); person1.setName("程光強"); person1.setSex("男"); System.out.println(person1.getName()+"是"+person1.getSex()+"人!!!"); } }
測試結果:
FileSystemXmlApplicationContext將從指定文件的絕對路徑中尋找XML配置文件,找到並裝載完成ApplicationContext的實例化工做,看下面代碼:
package test; import dao.TestDao; import entity.Person; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import org.springframework.core.io.FileSystemResource; public class TestDemo { @Test public void test3(){ //初始化spring容器ApplicationContext,加載配置文件 ApplicationContext applicationContext = new FileSystemXmlApplicationContext("D:\\MyNewWorld\\Study_JAVA\\newspringdemo\\src\\main\\resource\\applicationContext.xml"); Person person1 = (Person) applicationContext.getBean("person1"); person1.setName("張三"); person1.setSex("男"); System.out.println(person1.getName()+"是"+person1.getSex()+"人!!!"); } }
測試結果:
在Web服務器實化ApplicationContext容器時,通常使用基於org.springframework.web.context.ContextLoaderListener的實現方式(須要將spring-web模塊導入項目中),此方法只需在web.xml中添加以下代碼:
<!-- spring-web --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.0.2.RELEASE</version> </dependency>
(1)、利用Listener接口來實現
<web-app> <!--加載src目錄下的applicationContext.xml文件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextCleanupListener</listener-class> </listener> </web-app>
(2)、利用Servlet接口來實現
<web-app> <!--加載src目錄下的applicationContext.xml文件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <servlet> <servlet-name>context</servlet-name> <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>context</servlet-name> <!--處理全部URL--> <url-pattern>/</url-pattern> </servlet-mapping> <!--處理中文亂碼--> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--設置訪問靜態資源--> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.css</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.js</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.jpg</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.png</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.gif</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.mp3</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.mp4</url-pattern> </servlet-mapping> </web-app>
本篇對Spring IoC和DI的講解到此結束,但願你們能有所收穫。
【原創聲明】:本人原創:https://www.cnblogs.com/zyx110/