Spring依賴注入(IOC)那些事


     小菜使用Spring有幾個月了,但是對於它的內部原理,卻是一頭霧水,這次借着工作中遇到的一個小問題,來總結一下Spring。

     Spring依賴注入的思想,就是把對象交由Spring容器管理,使用者只需聲明什么時候需要對象 ,這個可以說是常識,在這就不多說啦。

     小菜的項目中,為了提高代碼運行效率,需要在類實例化的時候初始化一個列表,避免重復查詢,於是小菜想當然的寫了如下代碼:

 1 @Component
 2 public class ApplyStatusHandler{
 3   @Autowired
 4   private DictMgr dictMgr;
 5   @Autowired
 6   private ApplyMgr applyMgr;
 7     
 8   public ApplyStatusHandler(){
 9       //這里初始化列表,使用了dictMgr、applyMgr
10   }
11 }

 

     但實際測時,發現列表是空的。。。小菜剛開始還以為是構造方法沒有執行,但通過異常捕獲發現原來是出現了空指針。

     接下來分析一下為啥會出現空指針。

     @Component注解,意思大致就是告訴Spring,要把ApplyStatusHandler類的對象放到容器里,以后可以方便的使用@Autowired進行注入。

     @Autowired注解,有以下兩個重要特點:

        

          +可以對成員變量方法構造函數進行標注,來完成自動注入

          +根據類型進行自動注入的,如果spring配置文件中存在多個相同類型的bean時,或者不存在指定類型的bean,都會拋出異常。

 

     其中,對成員變量的注解,就如上例所示,可以直接從Spring容器中拿到此類型的對象,注入到成員變量中。

     對方法的注解,小菜的理解就是對方法的參數進行初始化。例如:

1 @Autowired
2 public void initXXXX(DictMgr dictMgr){
3   //這里可以拿到DictMgr類的對象dictMgr
4 }

 

     此方法因為有@Autowired標識,所以Spring會自動執行此方法,並且在執行的時候,去自己的容器找尋找和該方法參數類型一致的對象,進行注入,這樣在方法中就可以拿到需要的對象了,其實和成員變量的注解大同小異,只不過把變量換了一個地方而已。

     對於以上兩種方法,有一個必要的前提:對象必須是存在的(ApplyStatusHandler類的對象)!

     很容易理解,無論是對成員變量的注入,還是對方法參數的注入,都必須保證變量所在的對象是存在的,否則無從注入。

     到這,讀者應該能明白為什么會出現空指針,因為Spring先調用的構造方法,此時還沒有進行注入。

     幸好還有構造方法注入(和方法注入一樣的道理),既然是構造方法注入,那么在Spring調用構造方法時,應該就可以拿到對象,然后再使用,就不會出現空指針,於是小菜把代碼改成如下形式:

 1 @Component
 2 public class ApplyStatusHandler{
 3   
 4   private DictMgr dictMgr;
 5   private ApplyMgr applyMgr;
 6     
 7   @Autowired
 8   public ApplyStatusHandler(DictMgr dictMgr,ApplyMgr applyMgr){
 9       this.dictMgr=dictMgr;
10       this.applyMgr=applyMgr;
11     
12       //這里初始化列表,使用了dictMgr、applyMgr
13   }
14     
15 }

 

     小菜滿懷信心的啟動項目,的確是沒報空指針異常,但卻報了很多Spring內部的異常。。。

     經過一番搜索,原來是由於小菜聲明了一個帶參數的構造方法,導致默認的無參數構造方法被抹掉,而這種情況下Spring實例化ApplyStatusHandler類,必須要有無參數的構造方法,因此加上即可(方法中可以什么也不做,但必須要有):

1 public ApplyStatusHandler(){}

 

     這下再啟動項目,完美運行,說明對象已經成功注入到了構造方法中。

     如果我們不繼續思考,事情可能就到此結束了,但是:既然這個無參構造方法是必須的,就說明Spring必然要調用這個方法,但調用了無參的構造方法,小菜寫的有參構造方法是怎么調用的呢?總不會同時調用兩個吧?

     其實,這和Spring底層的實例化方式有關。

     讀者可能非常了解什么依賴注入,交由Spring容器管理,但底層究竟是怎么實現的呢?

     據小菜不完全了解,應該是有兩種實現方式:JDK動態代理和Cglib動態代理。

     JDK動態代理,需要實現InvocationHandler 接口,也就是說如果想使用這種代理方式創建對象,需要讓類先實現InvocationHandler 接口才行,最終創建的對象是一個新類的對象。

     Cglib動態代理,采用的是繼承方式,它會在底層創建一個類,來繼承原有的類,但是這個子類所有的方法都是直接調用父類去實現,相當於父類的一個代理、封裝(封裝的目的是支持事務處理),實際上我們在程序中使用的是這個子類的對象,並不是ApplyStatusHandler的對象。

     通過這兩種代理方式,才讓Spring可以支持事務、管理對象。

     本例中,小菜的這個類並沒有實現InvocationHandler 接口,也就是說,不會使用JDK動態代理,而是使用Cglib動態代理來實例化對象,因此Spring會創建一個類來繼承ApplyStatusHandler,然后根據ApplyStatusHandler類的構造方法實例化ApplyStatusHandler,再把子類實例化,讓子類持有這個父類的引用,最終注入到變量中的是子類。

     由此可以看出,我們通過在構造方法上使用@Autowired注入對象是正確的,ApplyStatusHandler類能成功實例化,但由於有子類需要繼承ApplyStatusHandler,因此ApplyStatusHandler中必須有一個空的構造方法,否則子類是無法實例化的(java基礎。。。)。

     總之,ApplyStatusHandler類中的無參構造方法,是用來實例化Cglib生成的代理子類;有參構造方法是為了完成注入。

     好啦,小菜的分享到此結束~~

     水平有限,高手勿噴

 

     為了方便讀者研究,小菜貼出一些鏈接供讀者參考:

 

          +通過CGLIB實現AOP的淺析

          +java 動態代理proxy VS cglib的動態代理的區別

          +Spring注解注入

          +能不能在spring中首先用構造函數方式注入,然后再用setter注入

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM