深入瞭解Spring IoC

IoC全稱Inversion of Control即控制反轉,它還有一個別名依賴注入。spring利用Ioc容器幫我們自動構建對象及注入依賴對象,減少了對象構建與業務代碼的耦合,使得我們能夠更加高效愉快的寫bug🐞了( ̄▽ ̄)"。接下來我們詳細介紹下這個spring Ioc吧。

依賴注入原理

1.三種依賴注入方式

spring中有三種常見的依賴注入方式即:構造方法注入、setter方法注入、接口注入。其中前兩種注入方式是我們現在仍然用比較多的,而最後一種由於其需要侵入代碼,所以已經很少用了,這裏就不介紹了。
(1) 構造方法注入:
這是我們經常能看到的注入方式,即通過對象構造器參數注入依賴對象。這種方式比較直觀,同時構造完成後對象即進入就緒狀態可以使用了。
(2)setter方法注入
在我們java bean對象中,經常會通過getter和setter方法獲取和設置對象的屬性,這些方法統稱爲setter方法,通過爲依賴對象添加setter方法,容器就會幫我們實現依賴對象的注入。其中我們比較常用的@Autowired就屬於這種方式。不過這種方式的注入,不能保證對象構造完成後就立馬進入就緒狀態。

在idea裏當我們使用@Autowired時經常能看到"Filed injection is not recommended"的提示,告訴我們不推薦使用@Autowired進行注入。其主要原因是@Autowired的注入僅僅適用於Ioc容器,而當我們在程序中直接使用new去構造對象時,對象中的@Autowired依賴是無法自動注入的,就可能存在npe的風險。

2.BeanFactory和ApplicationContext

在講兩個主角之前,我們首先講下IoC Service Provider。上面介紹了三種依賴的注入方式,但我們需要的是相應的角色或服務來幫我們實際的實現對象的構建和與依賴的注入,而IoC Service Provider就是這個角色。它是一個抽象的概念,可能是一段代碼也可能是一組類,它主要負責業務對象的構建管理、業務對象間的依賴綁定。在spring中承擔這個角色的主要就是BeanFactory和ApplicationContext,當然他們也承擔着容器類的角色,我們這裏只着重講解他們作爲IoC Service Provider的功能,他們作爲容器的功能會在下面講解。
這兩個類中BeanFactory比較古老了,它默認採用延遲加載策略,即只有當需要訪問容器中的受管對象時,纔會對受管對象進行初始化及依賴注入操作,所以項目啓動比較快。而ApplicationContext是目前項目中比較常用的,它繼承了BeanFactory並增加了其他很多高級特性。ApplicationContext所管理的對象,默認在容器啓動之後全部進行初始化和綁定操作,所以其啓動速度會相對慢些,不過隨着spring和java的不斷優化和技術升級,這個啓動時間一般都可以接受,而其提供的很多特性非常大的方便了我們的開發,所以目前我們大部分的項目都是使用的ApplicationContext。

3.依賴注入過程

上面已經介紹了我們常用的注入方式及幫我實現注入的角色,接下來我們就可以介紹下依賴注入的過程了🤡🤠。
(1)首先對容器來說,要實現依賴注入它最需要的是對象的信息及對象間的依賴關係,spring通常會通過XML或註解等方式記錄這些信息。我們在一些比較古來的項目中還能看到這種XML配置文件,如下所示:

<bean id="djNewsProvider" class="..FXNewsProvider"> 
     <property name="newsListener">
         <ref bean="djNewsListener"/>
    </property>
     <property name="newPersistener">
         <ref bean="djNewsPersister"/>
     </property>
     
</bean>
<bean id="newsListener" class="..FXNewsProvider"> 
</bean>
<bean id="newPersistener" class="..FXNewsProvider"> 
</bean>

上面是我們在些老項目中常見的配置方式,在java5支持註解後,我們現在使用的更多是通過註解來代替這些XML配置。例如現在我們通過@Component、@Service等來標註對象信息,用@Autowired、@Resource來標註當前對象的依賴對象信息。ApplicationContext在項目啓動時會通過我們配置的scanning-path自動的去尋找這些對象並解析保存其信息及依賴關係。
(2)spring收集到對象信息和依賴關係後會將這些信息封裝到BeanDefinition中,每個容器中受管對象都會有一個BeanDefinition,它記錄了對象的所有必信息。包括對應的Class類型、是否爲抽象類、構造方法參數及依賴關係等。最後這些數據會被註冊到BeanDefiniteRegistry中。
(3)當某個請求通過BeanFactory或ApplicationContext獲取對象時(getBean()),就會開始對象的實例化了。對象在實例化的過程中會先獲取對象的BeanDefinition,然後採用"策略模式",通過反射或者cglib動態代理來初始化對象(注意這裏只是初始化,依賴對象還沒注入)。
(4)在完成初始化後容器會通過BeanWrapper包裹住實例,然後通過BeanWrapper來實現對象屬性值的設置和依賴的對象的注入。BeanWrapper根據實例的依賴對象到BeanFactory或ApplicationContext中獲取相關對象,然後set進對象,這樣就完成了實例的依賴注入。

我們可以再來看下(3)(4)的僞代碼:

// 通過反射構建對象
Object provider = Class.forName("...Provider").newInstance();
Object listener = Class.forName("...Listener").newInstance();
        
BeanWrapper newProvider = new BeanWrapperImpl(provider);
// 注入依賴對象
newProvider.setPropertyValue("listener", listener);

整個(1)(2)(3)(4)就是容器中依賴注入的詳細流程了,是不是比想象中的要簡單呢ヘ|・∀・|ノ*~●

Spring容器揭祕

IoC容器是Spring框架的重要組成部分。它通過加載配置數據並利用這些信息構造綁定容器內的所有對象,最終組裝成一個可用的基於輕量級容器的應用系統。spring容器功能的實現主要分爲兩個過程:容器啓動階段Bean實例化階段

1.容器啓動階段

上面也大致介紹過了,實際上容器的啓動階段主要就是通過某些特定的工具類來收集配置信息,並將解析後的信息封裝爲BeanDefinition,最後註冊到相應的BeanDefinitionRegistry中,總的來說就是進行對象管理信息的收集。
在這個階段spring爲我們提供了一種叫BeanFactoryPostProcess的擴展機制來插手容器的啓動,它可以對容器中的BeanDefinition進行修改,比如修改Bean定義的屬性、爲Bean增加其他信息等。
其中有一個使用的非常普遍的功能就是系統屬性值的替換:我們經常可以在項目文件中看到properties文件,這裏面經常會存放數據庫密碼賬號等經常發生變化的配置數據,這些配置數據本來應該是在XML中配置的,但是我們通過${jdbc.url}這種方式將實際的數據配置在properties文件中。這個功能就是BeanFactoryPostProcess來幫我們實現,它修改了BeanDefinition的數據,用properties文件中數據替換了BeanDefinition中的${}內的數據。是不是很好玩🤣。

2.Bean實例化階段

容器啓動後並不會馬上實例化Bean,而是需要等到客戶端調用BeanFactory或ApplicationContext自動調用getBean方法獲取對象時纔會真正的實例化bean。我們可以先看下Bean實例化的過程。在這裏插入圖片描述
當客戶端調用了getBean方法後,就是開始了Bean的整體的實例化流程。

  1. 首先第一步進行的是對象的初始化,這個階段就是上面介紹過的通過cglib或者反射來初始化對象,這裏只是初始化,依賴對象還沒注入。
  2. 這階段進行的是依賴對象的注入,依賴對象的注入我們在上面介紹過,主要是通過BeanWrapper來實現的。
  3. 在之後會檢查Aware相關接口並設置相關依賴,比如Bean名稱的設置,以及我們比較常用的ApplicationContextAware,這個Aware接口可以將容器類ApplicationContext的引用注入對象中,這樣我們就可以方便的通過ApplicationContext.getBean()獲取我們需要的對象了。
  4. 接下來是重要的BeanPostProcessor了,它分爲Pre和Post兩種,在圖中可以看到,這兩個processor分別處在Bean實例化(調用構造方法)前和實例化(調用構造方法)後,它類似容器啓動階段的BeanFactoryPostProcessor可以用來動態修改擴展Bean的數據信息。其中一個最重要的應用就是Spring Aop的動態代理,Aop的動態代理就是通過BeanPostProcessor來實現的,它利用反射或cglib通過BeanPostProcessor生成了實例的代理對象並直接返回給了調用方完成了動態代理。
  5. 接下來在兩個BeanPostProcessor間,對象實例化(調用構造方法)前,spring會檢查對像是否實現了InitializingBean接口,如果是就會調用afterPropertiesSet()方法進一步調整對象狀態,除此之外我們常用的 @Bean(initMethod = “func”)中initMethod方法的執行也是在這裏實現的。
  6. 最後階段就是在容器關閉Bean生命週期結束時,檢查Bean實現的destory-method,其中 @Bean(destroyMethod = “func”)中的destroyMethod方法也是在這裏執行的。

好了,至此我們們就把容器的啓動及Bean的實例化講完了,舒服了🍑🍓🍎🍐。下面在簡單講下Bean的生命週期。

3.Bean的生命週期

容器除了會幫我們進行依賴注入外還會幫我們管理對象的生命週期,Bean的生命週期被稱爲Scope,可以理解爲對象的存活範圍或存活時間。bean的scope最常見的主要有兩種:singleton和prototype。
(1)singleton:
這種類型是我們最常見的。被標記爲singleton scope的對象在容器中只存在一個實例,所有對該對象的引用都共享這個實例,它會一直存活到容器退出,幾乎和容器的生命週期一樣長。
(1)prototype:
被標記爲prototype scope的對象,在容器每次收到該對象的請求時,都會生產一個新的對象返回給請求方,並且放回給請求方後,容器就不會再擁有當前對象的引用,請求方需要自己負責該對象的生命週期。這種類型很像我們自己在程序中使用new生成的對象,這中對象在我們不在使用後,會被GC線程在何時的時機回收掉,結束掉他的一生。

除此之外還有些不太常見的類型,原理都是一樣的,這裏就不在贅述了。終於寫完了,嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻,打完收工,喫飯去🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章