坦白地說,從長(zhǎng)遠(yuǎn)來看,大多數(shù)團(tuán)隊(duì)都應(yīng)該遠(yuǎn)離如下的Java特性。不過凡事總有例外的情況。如果你有一個(gè)強(qiáng)大的團(tuán)隊(duì),總是能夠清楚地意識(shí)到自己在做什么,那就按照你的想法去做就行。但對(duì)于大多數(shù)情況來說,如果你在項(xiàng)目的開發(fā)中使用了下面這幾個(gè)Java特性,那么從長(zhǎng)遠(yuǎn)來看你是會(huì)后悔的。 這些應(yīng)該遠(yuǎn)離的Java特性有:
下面對(duì)這些特性進(jìn)行逐個(gè)分析,看看為什么普通的Java開發(fā)者應(yīng)該遠(yuǎn)離他們: 反射在流行的庫如Spring和Hibernate中,反射自然有其用武之地。不過內(nèi)省業(yè)務(wù)代碼在很多時(shí)候都不是一件好事,原因有很多,一般情況下我總是建議大家不要使用反射。 首先是代碼可讀性與工具支持。打開熟悉的IDE,尋找你的Java代碼的內(nèi)部依賴,很容易吧?,F(xiàn)在,使用反射來替換掉你的代碼然后再試一下,結(jié)果如何呢?如果通過反射來修改已經(jīng)封裝好的對(duì)象狀態(tài),那么結(jié)果將會(huì)變得更加不可控。請(qǐng)看看如下示例代碼: 如果這樣做就無法得到編譯期的安全保證。就像上面這個(gè)示例一樣,你會(huì)發(fā)現(xiàn)如果getDeclaredField()方法調(diào)用的參數(shù)輸錯(cuò)了,那么只有在運(yùn)行期才能發(fā)現(xiàn)。要知道的是,尋找運(yùn)行期Bug的難度要遠(yuǎn)遠(yuǎn)超過編譯期的Bug。 最后還要談?wù)劥鷥r(jià)問題。JIT對(duì)反射的優(yōu)化程度是不同的,有些優(yōu)化時(shí)間會(huì)更長(zhǎng)一些,而有些甚至是無法應(yīng)用優(yōu)化。因此,有時(shí)反射的性能損失可以達(dá)到幾個(gè)數(shù)量級(jí)的差別。不過在典型的業(yè)務(wù)應(yīng)用中,你可能不會(huì)注意到這個(gè)代價(jià)。 總結(jié)一下,我覺得在業(yè)務(wù)代碼中唯一合理(直接)使用反射的場(chǎng)景是通過AOP。除此之外,你最好遠(yuǎn)離反射這一特性。 字節(jié)碼操縱如果在Java EE應(yīng)用代碼中直接使用了CGLIB或是ASM庫,那么我建議你好好審視一下。就像方才我提到的反射帶來的消極影響,使用字節(jié)碼操縱所帶來的痛苦可能是反射的好幾倍之多。 更糟糕的是在編譯期你根本就看不到可執(zhí)行的代碼。從本質(zhì)上來說,你不知道產(chǎn)品中實(shí)際運(yùn)行的是什么代碼。因此在面對(duì)運(yùn)行期的問題以及調(diào)試時(shí),你要花費(fèi)更多的時(shí)間。 ThreadLocal看到業(yè)務(wù)代碼中如果出現(xiàn)ThreadLocal會(huì)讓我感到顫抖,原因有二。首先,借助于ThreadLocal,你可以不必顯式通過方法調(diào)用就可以傳遞變量,而且會(huì)對(duì)這種做法上癮。在某些情況下這么做可能是合理的,不過如果不小心,那么我可以保證最后代碼中會(huì)出現(xiàn)大量意想不到的依賴。 第二個(gè)原因與我每天的工作有關(guān)。將數(shù)據(jù)存儲(chǔ)在ThreadLocal中很容易造成內(nèi)存泄漏,至少我所看到的十個(gè)永久代泄漏中就有一個(gè)是由過量使用ThreadLocal導(dǎo)致的。連同類加載器及線程池的使用,“java.lang.OutOfMemoryError:Permgen space”就在不遠(yuǎn)處等著你呢。 類加載器首先,類加載器是個(gè)很復(fù)雜的東西。你必須首先理解他們,包括層次關(guān)系、委托機(jī)制以及類緩存等等。即便你覺得自己已經(jīng)精通了類加載器,一開始使用時(shí)還是會(huì)出現(xiàn)各種各樣的問題,很可能會(huì)導(dǎo)致類加載器泄漏問題。因此,我建議大家還是將類加載器留給應(yīng)用服務(wù)器使用吧。 弱引用與軟引用關(guān)于弱引用與軟引用,你是不是只知道他們是什么以及簡(jiǎn)單的使用方式而已?現(xiàn)在的你對(duì)Java內(nèi)核有了更好的理解,那會(huì)不會(huì)使用軟引用重寫所有的緩存呢?這么做可不太好,可不能手里有錘子就到處找鼓敲吧。 你可能很想知道我為什么說緩存不太適用使用軟引用吧。畢竟,使用軟引用來構(gòu)建緩存可以很好地說明將某些復(fù)雜性委托給GC來完成而不是自己去實(shí)現(xiàn)這一準(zhǔn)則。 下面來舉個(gè)例子吧。你使用軟引用構(gòu)建了一個(gè)緩存,這樣當(dāng)內(nèi)存行將耗盡時(shí),GC會(huì)介入并開始清理。但現(xiàn)在你根本就無法控制哪些對(duì)象會(huì)從緩存中刪除,很有可能在下一次緩存中不再有這個(gè)對(duì)象時(shí)重新創(chuàng)建一次。如果內(nèi)存還是很緊張,又觸發(fā)GC執(zhí)行了一次清理,那么很有可能會(huì)出現(xiàn)一個(gè)死循環(huán),應(yīng)用會(huì)占用大量CPU時(shí)間,F(xiàn)ull GC也會(huì)不斷執(zhí)行。 Socketsjava.net.Socket簡(jiǎn)直太難使用了。我認(rèn)為它的缺陷歸根結(jié)底源自其阻塞的本質(zhì)。在編寫具有Web前端的典型的Java EE應(yīng)用時(shí),你需要高度的并發(fā)性來支持大量的用戶訪問。這時(shí)你最不想發(fā)生的事情就是讓可伸縮性不那么好的線程池呆在那兒,等待著阻塞的Sockets。 現(xiàn)在已經(jīng)出現(xiàn)了非常棒的第三方庫來解決這些問題,別自己寫了,嘗試一下Netty吧。 Java出現(xiàn)至今經(jīng)歷了多次版本更迭,每次也都會(huì)有諸多新特性的加入。在日常的Java開發(fā)中,你認(rèn)為存在哪些Java特性是很容易導(dǎo)致問題的呢?作者提到不建議在普通的應(yīng)用開發(fā)中使用反射,不過對(duì)于一些框架或庫的開發(fā),離開反射實(shí)際上是無法實(shí)現(xiàn)的,例如Spring、Struts2等框架,那么在一般的Java項(xiàng)目開發(fā)中,你覺得哪些地方有使用反射的必要呢?換句話說,如果不使用反射就實(shí)現(xiàn)不了功能或是需求。文中作者也不建議使用字節(jié)碼操縱,實(shí)際上一些框架在實(shí)現(xiàn)某些功能時(shí)是必須要使用的,比如說Spring在實(shí)現(xiàn)AOP時(shí)就使用了Java的動(dòng)態(tài)代理與CGLib庫兩種方式來達(dá)成的。那么對(duì)于一般的Java項(xiàng)目來說,哪些地方需要用到字節(jié)碼操縱呢?歡迎各位讀者暢所欲言,一起討論這些有趣的話題。 |
|