記錄一下今天在幫同事解決使用spring參數注入問題的時候由于對泛型的理解不到位而遇到的坑。
如下代碼所示:
@RequestMapping(value="saveAll")
public ResponseMsg saveAll(List rules){
Rule rule=rules.get(0); //這行代碼在測試的時候報錯了
......
}
這段代碼的意思是使用spring的參數注入功能自動完成將前端傳過來的數據裝載到rules變量里面。
我剛開始一直認為這段代碼肯定是對的,然后就一直說同事一定是前端傳過來的數據有錯,然后就各種檢查js,debug一步一步的查,發現前端傳過來的數據是正確的,后來我又想會不會是eclipse的debug功能有缺陷(原諒一個idea粉對eclipse的各種不屑),當然繼續被打臉,因為我在我電腦上debug時數據是一樣的,rules里面的元素居然是LinkedHashMap!!看著debug顯示的數據,簡直不能接受,我明明聲明了rules對象只能存Rules對象啊,怎么會裝其他對象!!
然后就真的沒轍了。。我就說這個問題我解決不了了,超出我認知范圍啊,然后我們叫了一個正式員工過來幫我們看看。。他開始也是按我們的步驟排錯,后來遇到和我們一樣的問題,但是牛人終究是牛人,能想出來的導致問題的因素也比我們多,他說會不會是spring不支持這種帶泛型的自動參數裝載啊,畢竟泛型是要被擦除的。。
擦除。。。泛型擦除。。。我靠,我終于知道是什么原因了。之前看了那么多關于泛型擦除的居然都沒有想到是這個問題!!而且這種坑當時也踩過,居然沒聯想起來,智商捉急。
關于泛型擦除的詳細介紹具體是什么我就不寫在這篇文章里面了,大概就是在編譯前會執行一系列的語法檢查,從而減少因為強制類型轉換帶來的異常,但是編譯后的代碼是不含泛型的,會將泛型限制的元素類型給去掉。
也就是說雖然我聲明了rules只能裝Rule類型的對象,但是代碼被編譯后,這個限制就沒有了!因為通過語法檢查rules里的元素確實是Rule類型的對象,所以并不需要在編譯后再去檢查。但是問題來了,這種檢查只能檢查一些顯式生命的對象是不是Rule類型,而java是可以通過反射來動態的生成對象的,sprng在參數注入的時候是通過反射實現前端參數自動裝載入對象的相關屬性!!
所以這樣聲明的問題在于,由于編譯時對rules內元素類型的限制已經被擦除了,所以spring并不知道反射成那種類型的對象,于是就默認的用LinkedHashMap來裝載一個對象所有的屬性和值,于是rules里面的對象在運行的時候實際上是LinkedHashMap!!!所以spring可能并不支持泛型參數或者需要指定其他條件才能正確的注入泛型參數(這個還沒有深究)。
至于以前踩過這方面的坑就是用Gson反序列化帶泛型的對象的時候需要額外指定一個參數來說明集合里面的元素類型(具體的我忘了,這個有思路就好)。當時也是覺得很奇妙,為什么不做得智能一點自己識別,我不是已經通過泛型指定類型了么。當時也就抱怨一下,沒有怎么多想,現在想起來還真是too young , too simple。。
這件事讓我明白不要盲目的相信自己的經驗,計算機肯定是對的。經驗解決不了的問題,就從原理一步一步去想,平時學的理論可能看起來沒什么用,就好像科普一樣,然而在解決一些問題時確是一針見血。多聯想,發散思維才能在技術這條路上走得更遠。