作為一個(gè) JAVA 開(kāi)發(fā) ,Spring 是我用的最多的框架。Spring 這個(gè)名字起的很好,一語(yǔ)雙關(guān),在英文里 spring 是春天的意思,寓意程序員的春天,同時(shí) spring 還有彈簧的意思,它確實(shí)是一個(gè)有彈性的框架,擴(kuò)展性很強(qiáng)。
類型約束
提到 spring ,首先想到的就是 ioc 和 aop 了,這些動(dòng)態(tài)技術(shù)都是魔法,能自動(dòng)管理,能增強(qiáng)代碼。但是動(dòng)態(tài)技術(shù)也帶來(lái)了新的問(wèn)題,Java 本來(lái)是個(gè)靜態(tài)語(yǔ)言,有很強(qiáng)的類型約束,卻被動(dòng)態(tài)技術(shù)打破了。
@Cacheable(value = "userCache", key = "#corpId + #id")
public User findUserByCorpIdAndId(String corpId,String id){}
比如上面的代碼,我們可以使用 SpEL(Spring Expression Language)寫(xiě)一些簡(jiǎn)單的邏輯來(lái)構(gòu)建 key,但是 SpEL 是沒(méi)有類型約束的,編譯沒(méi)有檢查,一般的 IDE 也沒(méi)有提示(Idea旗艦版有做支持)。Spring 框架在很多地方都是支持 SpEL 的,但是總是手寫(xiě)這些字符串,很危險(xiǎn)。我就犯過(guò)錯(cuò)誤,寫(xiě)錯(cuò)了變量名但是程序仍然可以運(yùn)行,只是參數(shù)拼上去總是 null,導(dǎo)致不同的參數(shù)拿到的緩存數(shù)據(jù)總是相同的。使用 api 的形式可以避免這樣的問(wèn)題,但是便利性就下降了。
復(fù)雜度太高
Spring 可以說(shuō)是很好的踐行了面向接口和面向?qū)ο缶幊蹋灾掠诶^承和依賴關(guān)系復(fù)雜到讓人嘆為觀止。對(duì)于 Spring 的源代碼我是不建議去讀的,因?yàn)閺?fù)雜看很久沒(méi)有頭緒,方法跳了很久也沒(méi)有到真正的實(shí)現(xiàn),由于大量應(yīng)用了動(dòng)態(tài)字節(jié)碼技術(shù),有很多生成代碼的代碼,極為繁瑣。原理了解下即可,花太多時(shí)間去讀源碼,收益不大。不過(guò)對(duì)于一個(gè)框架來(lái)說(shuō),要解決復(fù)雜問(wèn)題,自身也必然復(fù)雜。但是如果一旦遇到報(bào)錯(cuò)要排查,或者調(diào)試代碼就很頭大了。
隨著生態(tài)的發(fā)展,Spring 已經(jīng)是一個(gè)非常龐大的框架,學(xué)習(xí)曲線比較陡峭,需要一定的學(xué)習(xí)成本才能熟練使用,要掌握它是非常困難的。也因此發(fā)展出了 Spring 八股文,大量的相關(guān)面試題攻略出現(xiàn)在互聯(lián)網(wǎng)上。
資源占用高性能差
Spring 是一個(gè) Bean 容器,可以讓我們避免過(guò)多的創(chuàng)建對(duì)象,由 Spring 來(lái)管理,還可以自動(dòng)注入我們需要的對(duì)象實(shí)例。但是,Bean 的管理也是需要成本的,在啟動(dòng)階段就要掃描所有的包或配置文件來(lái)讀取配置的 Bean,完成 Bean 的實(shí)例化再進(jìn)行依賴注入和初始化,這就導(dǎo)致了規(guī)模一大啟動(dòng)時(shí)間就會(huì)很長(zhǎng),而且一開(kāi)始就占用大量的內(nèi)存空間來(lái)存儲(chǔ)實(shí)例。而框架內(nèi)部大量使用反射,也會(huì)降低運(yùn)行時(shí)的性能。
根據(jù)我個(gè)人的經(jīng)驗(yàn),一個(gè)中等規(guī)模的 Spring Boot 項(xiàng)目至少需要 1G 的內(nèi)存。部署一個(gè) Spring Boot 實(shí)例耗費(fèi)的內(nèi)存至少可以部署五六個(gè) vert.x 實(shí)例。我負(fù)責(zé)的部分項(xiàng)目啟動(dòng)時(shí)間最長(zhǎng)的已經(jīng)來(lái)到了1分半。
不可靠性
Spring 使用了大量的動(dòng)態(tài)技術(shù),動(dòng)態(tài)技術(shù)來(lái)帶來(lái)了很多坑,這些坑甚至還成為了經(jīng)典面試題,比如“spring事務(wù)失效的12種場(chǎng)景”。有坑是常見(jiàn)的,大部分時(shí)候解決問(wèn)題的方案也會(huì)帶來(lái)新的問(wèn)題。但是 spring 的坑有點(diǎn)多,不光是事務(wù),凡是申明式注解,都會(huì)有不生效的問(wèn)題,有時(shí)候不生效很難察覺(jué),測(cè)試也不容易測(cè)出來(lái),比如緩存和校驗(yàn),沒(méi)有生效結(jié)果也很可能是對(duì)的。也因此,有些公司的規(guī)范里是禁止使用申明式事務(wù)的。你可能會(huì)說(shuō)經(jīng)驗(yàn)多了就好了,多注意點(diǎn),老虎也有打盹的時(shí)候,何況開(kāi)發(fā)人員工作強(qiáng)度那么大,心中難免會(huì)不踏實(shí)。
不可否認(rèn)的是動(dòng)態(tài)技術(shù)帶來(lái)了便利,假如我們想加代碼,可以不用修改原來(lái)的邏輯,單獨(dú)寫(xiě) aop 攔截邏輯就可以了。但是,這樣也帶來(lái)了很多不確定性,一段方法執(zhí)行了哪些邏輯,我們可能并不知道。aop 技術(shù)還是盡量要少用的,寫(xiě)的多了自己也忘記加過(guò)什么東西,將來(lái)接手項(xiàng)目的人可能要叫苦了。Spring Boot 的自動(dòng)配置還有可能帶來(lái)安全問(wèn)題,依賴的 jar 包如果有惡意代碼,是可以通過(guò)自動(dòng)配置直接在啟動(dòng)階段執(zhí)行的。
反思
Spring 并不完美,動(dòng)態(tài)字節(jié)碼技術(shù)也許不是好的解決方案,ioc 和容器技術(shù)不是必須的,這套方案只是在 Java 中比較流行而已。動(dòng)態(tài)技術(shù)提升了靜態(tài)語(yǔ)言的靈活性,同時(shí)又失去了靜態(tài)語(yǔ)言的嚴(yán)謹(jǐn),編譯期很多錯(cuò)誤都檢查不出來(lái),只有跑起來(lái)了才能知道。
雖然 ioc 降低了耦合性,但是我覺(jué)得并不是必須的,現(xiàn)實(shí)是我們的代碼常常沒(méi)有多實(shí)現(xiàn),不需要無(wú)縫替換,也沒(méi)有必要熱插撥。大項(xiàng)目為了保險(xiǎn)起見(jiàn)能跑就行,也不敢切換實(shí)現(xiàn),更高一層的抽象還會(huì)導(dǎo)致失去部分原生特性,過(guò)渡設(shè)計(jì)也會(huì)增加程序的復(fù)雜度,不要總是面向?qū)ο蠛兔鎸?duì)接口。
總結(jié)
我不反對(duì)使用 Spring ,畢竟對(duì)于這么有影響力的框架,如果團(tuán)隊(duì)達(dá)不成一致,用它可以減少爭(zhēng)議,大家都服氣。但是,我已經(jīng)不想再用了,探索別的方案對(duì)于我來(lái)說(shuō)更有吸引力。
沒(méi)有必要始終如一的使用一個(gè)框架,更沒(méi)有必要死磕一種語(yǔ)言,Java 確實(shí)有很多不便之處,這個(gè)我們要承認(rèn),但是動(dòng)態(tài)字節(jié)碼和代理技術(shù)不是良藥。多了解下其它的語(yǔ)言,開(kāi)闊下視野很有必要,有時(shí)候格局就這樣打開(kāi)了。






