小菜使用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生成的代理子類;有參構造方法是為了完成注入。
好啦,小菜的分享到此結束~~
水平有限,高手勿噴
為了方便讀者研究,小菜貼出一些鏈接供讀者參考:
+java 動態代理proxy VS cglib的動態代理的區別
+能不能在spring中首先用構造函數方式注入,然后再用setter注入