小菜使用Spring有幾個月了,可是對於它的內部原理,倒是一頭霧水,此次藉着工做中遇到的一個小問題,來總結一下Spring。html
Spring依賴注入的思想,就是把對象交由Spring容器管理,使用者只需聲明何時須要對象 ,這個能夠說是常識,在這就很少說啦。java
小菜的項目中,爲了提升代碼運行效率,須要在類實例化的時候初始化一個列表,避免重複查詢,因而小菜想固然的寫了以下代碼: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 }
但實際測時,發現列表是空的。。。小菜剛開始還覺得是構造方法沒有執行,但經過異常捕獲發現原來是出現了空指針。app
接下來分析一下爲啥會出現空指針。函數
@Component註解,意思大體就是告訴Spring,要把ApplyStatusHandler類的對象放到容器裏,之後能夠方便的使用@Autowired進行注入。this
@Autowired註解,有如下兩個重要特色:spa
+能夠對成員變量、方法和構造函數進行標註,來完成自動注入。.net
+是根據類型進行自動注入的,若是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生成的代理子類;有參構造方法是爲了完成注入。
好啦,小菜的分享到此結束~~
水平有限,高手勿噴
爲了方便讀者研究,小菜貼出一些連接供讀者參考: