轉(zhuǎn)載請(qǐng)注明出處即可瞒斩。
這不是一篇講Spring源碼解析的文章,也不是剖析Spring內(nèi)部設(shè)計(jì)的文章篷扩。只是在閱讀中的一些思考稠氮。
一曹阔、為什么很多優(yōu)秀框架或系統(tǒng)源碼感覺難以閱讀或理解
(1) 時(shí)間的考驗(yàn)
??任何優(yōu)秀的框架、系統(tǒng)和編程語言等都是經(jīng)歷了很長時(shí)間的考驗(yàn)隔披,比如Spring 1.0的這篇距今也有14年的時(shí)間次兆。Python和Linux的出現(xiàn)也要早于utf8。這也是Python2對(duì)字符串操作一直被人詬病的原因之一锹锰。
??長時(shí)間的發(fā)展也會(huì)讓最初可能簡單的邏輯芥炭,變得非常復(fù)雜。比如早期版本的Linux只有1萬多行代碼恃慧,現(xiàn)在擁有了超過3000萬行的代碼量园蝠。
??正如上圖對(duì)比的那樣,隨著時(shí)間的發(fā)展痢士,優(yōu)秀項(xiàng)目的代碼量會(huì)越來越多彪薛,并且越來越復(fù)雜。導(dǎo)致如果直接去看源碼很容易抓不住重點(diǎn)怠蹂,把思維淹沒在各種細(xì)節(jié)之中不能自拔善延。
??但是無論版本功能如何迭代,最初的核心設(shè)計(jì)思想是不會(huì)進(jìn)行變化的城侧。比如Linux 0.11就有了進(jìn)程易遣。Python Flask的路由和始終和早期版本的實(shí)現(xiàn)幾乎一樣沒有變化。所以如果直接打開Spring5的源碼一臉蒙圈的話嫌佑,其實(shí)可以去下載Spring早期版本的源碼去看下設(shè)計(jì)思想和原理豆茫,因?yàn)榇a量和功能的減少,更容易抓住核心設(shè)計(jì)思想屋摇,而不是和部分細(xì)節(jié)糾纏不清揩魂。
回到我們的主題Spring
spring-beans-1.2
spring-context-1.2
spring-core-1.2
因?yàn)榘姹竞芾希枰螺djar包炮温,然后在add jar到項(xiàng)目中(當(dāng)然這三個(gè)包肯定是跑不起來的火脉,還需要依賴apache和logger等其他相關(guān)的依賴包,本小節(jié)結(jié)尾會(huì)附錄老版本源碼下載地址)柒啤。
我們看下老版本的使用, 在使用上幾乎沒有變化倦挂。
我們?cè)诳聪?code>ApplicationContext的類圖,并和Spring5的做一個(gè)對(duì)比
當(dāng)然還可以對(duì)比下類的數(shù)量白修,Spring 1.2的也會(huì)少很多, 但核心的接口也都在妒峦。
讀到這里,如果陷入了Spring5源碼的泥潭不得主線兵睛,那么推薦閱讀下老版本的源碼肯骇,去理解下早期"最核心"的那些框架思想。
Spring老版本源碼下載地址
Spring Framework-1.2源碼
(2) 因?yàn)楹唵嗡詮?fù)雜
在技術(shù)領(lǐng)域祖很,簡潔的背后一定有很復(fù)雜的邏輯笛丙。
用個(gè)支付領(lǐng)域的例子,看似簡單的支付界面假颇,背后其實(shí)擁有很復(fù)雜的邏輯胚鸯。
Linux系統(tǒng)雖然3000W+的代碼量,但系統(tǒng)調(diào)用依然維持在200+的數(shù)量笨鸡,而MacOS的系統(tǒng)調(diào)用還不到200。JVM的指令也在200+敌土。
Spring其實(shí)也一樣算吩,ApplicationContext的方法雖然不多,但擁有很長的繼承鏈辙浑,和很多技術(shù)細(xì)節(jié)。
所以使用起來很簡單的東西拟糕,其內(nèi)部為了簡化外部的使用判呕,會(huì)做很多封裝。但是無需被背后復(fù)雜的邏輯阻斷源碼閱讀的進(jìn)程送滞,還是有一些其他手段來解決對(duì)繁雜代碼的理解侠草。
(3) 一團(tuán)漿糊
??閱讀源碼的時(shí),經(jīng)常會(huì)發(fā)現(xiàn)看的細(xì)節(jié)越來越多犁嗅,越往后就可能記不清楚前面的邏輯边涕。這個(gè)是十分正常的現(xiàn)象。因?yàn)樵谌说拇竽X進(jìn)行記憶并進(jìn)行分析的特征(機(jī)器學(xué)習(xí)里面的概念愧哟,也可以叫做屬性)是有限的奥吩。
??當(dāng)特征(體重、翼展等)數(shù)量不是很多時(shí)蕊梧,相關(guān)專家可以根據(jù)在腦海中記憶的經(jīng)驗(yàn)霞赫,來判斷當(dāng)前鳥屬于哪個(gè)種屬。專家系統(tǒng)的作用也是類似肥矢,有專門對(duì)這些特征處理端衰,判斷"經(jīng)驗(yàn)",來確定種屬甘改。但是當(dāng)特征數(shù)很多旅东,比如上萬個(gè)特征,數(shù)十萬的特征時(shí)十艾,人腦遠(yuǎn)不能處理這么海量的數(shù)據(jù)抵代,更不用說去分析了。所以這個(gè)時(shí)候忘嫉,機(jī)器學(xué)習(xí)和深度學(xué)習(xí)就起到了它們應(yīng)有的作用荤牍。
??在閱讀源碼時(shí),一般會(huì)通過記憶庆冕,然后理清順序的執(zhí)行流程康吵,然后加上之前的記憶整理和分析,得出某個(gè)功能的實(shí)現(xiàn)邏輯或者設(shè)計(jì)思想访递。但是如果在閱讀鏈路比較長的情況下晦嵌,找不到分析的切入點(diǎn)是很正常的情況,甚至在后續(xù)的閱讀中導(dǎo)致前面的記憶開始模糊不清。在這種情況下惭载,硬著頭皮記筆記旱函,畫類圖是解決記憶模糊和簡化理解的方式之一。
??當(dāng)然還可以換一種方式去理解源碼棕兼,比如陡舅,將理解源碼的過程分為兩步,第一, Spring 對(duì)外提供的接口伴挚,包含了哪些功能, 并且這些功能上有什么分類(不是ioc, aop這種分類,后面小節(jié)會(huì)詳述)灾炭。第二, 將第一步分類后的接口茎芋,去查看繼承鏈和引用關(guān)系,控制只在一個(gè)分類中進(jìn)行閱讀, 降低整體功能多導(dǎo)致的記憶蜈出、理解田弥、分析的問題。當(dāng)然如果還可以繼續(xù)分類的話铡原,還可以進(jìn)一步降低閱讀的難度偷厦。
??如果在從某一個(gè)方法調(diào)用開始打斷點(diǎn)后,一直往下讀燕刻,讀不懂的話只泼,可以試試這個(gè)方式。并且不用擔(dān)心會(huì)丟失整體的邏輯卵洗。因?yàn)樗械募夹g(shù)都是某種組合请唱。這意味著任何具體技術(shù)都是由當(dāng)下的部件、集成件或系統(tǒng)組件構(gòu)建或組合而成的过蹂。其次十绑,技術(shù)的每個(gè)組件自身也是微縮的技術(shù)。
(4) 明白了執(zhí)行過程酷勺,但感覺沒抓住設(shè)計(jì)思想
借用《如何閱讀一本書》里面的閱讀層次劃分本橙,閱讀源碼的也應(yīng)該有四個(gè)層次,可以對(duì)標(biāo)下自己的源碼理解在哪個(gè)層次脆诉。
a. 基礎(chǔ)閱讀
在這個(gè)層次甚亭,應(yīng)該可以熟練的編寫Java代碼,并且熟悉常用的類库说,反射狂鞋,動(dòng)態(tài)搭理、設(shè)計(jì)模式潜的,面向?qū)ο蟮脑O(shè)計(jì)思想等相關(guān)內(nèi)容骚揍。這些是閱讀Spring Frameworks的必備知識(shí),否則很容易知其然不知所以然。比如大家都看過NBA信不,但是不同人看到的NBA是不同的嘲叔,比如一個(gè)妙傳,大部分人都認(rèn)為是好球抽活,但是在教練看來就可能是某種戰(zhàn)術(shù)的成功硫戈。在球員的眼里也不一樣。
這本書在微信閱讀里面有中文版下硕。
b. 檢視閱讀
檢視閱讀是系統(tǒng)化略讀的一門藝術(shù)丁逝。在這個(gè)階段,也是建立橫向邏輯(后續(xù)小節(jié)會(huì)詳述)的一個(gè)過程梭姓。需要能找到相對(duì)獨(dú)立的模塊霜幼,并且找到模塊與模塊之間的關(guān)聯(lián)關(guān)系。這里不進(jìn)行詳述誉尖,后面再說罪既。
c. 分析閱讀
分析閱讀是指,一直要讀到對(duì)整個(gè)源碼深刻的理解铡恕。既能理解整體架構(gòu)琢感,又能理解核心技術(shù)細(xì)節(jié)。當(dāng)然在閱讀的過程中可能會(huì)有疑惑探熔,這個(gè)在后續(xù)章節(jié)會(huì)描述解決疑惑的方法驹针。
d. 主題閱讀
主題閱讀也叫做比較閱讀,也就是說需要閱讀其他的類似的框架源碼祭刚,并不只是Spring牌捷。不僅僅要能列出不同框架的相關(guān)之處,也能對(duì)不同框架優(yōu)缺點(diǎn)有深刻的理解涡驮。藝術(shù)書并不能保證你閱讀之后成為藝術(shù)家暗甥,只能告訴你其他藝術(shù)家用過的工具、技術(shù)和思維過程捉捅。當(dāng)然閱讀Spring也不能擔(dān)保成為好的軟件工程師撤防。但主題閱讀的這個(gè)階段,就已經(jīng)是對(duì)原理和思想內(nèi)化的過程棒口,也是整個(gè)源碼閱讀中最有收貨的寄月。因?yàn)楂@益良多,所以絕對(duì)值得花時(shí)間去努力做到這個(gè)層次无牵。
(5) 好代碼潛規(guī)則
??整潔的代碼簡單直接漾肮。整潔的代碼如同優(yōu)美的散文。整潔的代碼從不隱藏設(shè)計(jì)者的意圖茎毁,充滿了干凈利落的抽象和直截了當(dāng)?shù)目刂普Z句克懊。
??好的代碼是一種藝術(shù)忱辅,Spring更是如此。雖然代碼量相對(duì)較大谭溉,但Spring依然保持了十分整潔的代碼墙懂。比如所以類和方法的命名都有其正確的意義。就算單獨(dú)的一個(gè)方法名看不出來含義扮念。上下邏輯中依然保持了足夠的上下文用于理解损搬。并且函數(shù)的"擺放"位置也十分的"考究"。
以SimpleAliasRegistry
為例(在不考慮setter和getter的情況下)
方法checkForAliasCircle
一定在文件最后一個(gè)調(diào)用方法的下面柜与。
registerAlias
, resolveAliases
兩個(gè)方法都調(diào)用了checkForAliasCircle
但由于在文件中最后一個(gè)調(diào)用巧勤,所以放在了resolveAliases
的后面。
二旅挤、如何在閱讀源碼過程中建立邏輯
(1) 什么是邏輯
??邏輯是指把源碼的理解組織起來踢关,這個(gè)也是達(dá)到檢視閱讀和分析閱讀的必要步驟。邏輯分為兩個(gè)分類粘茄。縱向邏輯是指開發(fā)都能看懂的因果關(guān)系秕脓。因?yàn)锳柒瓣,所以B, 因?yàn)锽吠架, 所以C芙贫。橫向邏輯是指開發(fā)能看懂的總分關(guān)系,沒有遺漏和重復(fù)傍药。A包括B和C磺平。
(2) 縱向邏輯
縱向邏輯是指,因?yàn)锳所以B拐辽,既達(dá)到某個(gè)源碼細(xì)節(jié)(方法, 類)的邏輯達(dá)到可以理解的地步拣挪。
縱向邏輯薄弱一般有三個(gè)原因。
a. 前提條件不同
雖然是因?yàn)锳俱诸,所以B菠劝,但是還有A', A''等隱含條件。
比如下面這段代碼
睁搭,
通過這個(gè)可以說ApplicationContext接口的實(shí)現(xiàn)類提供了通過通配符讀取Resource數(shù)組的功能(這個(gè)是錯(cuò)誤的說法)赶诊。看到這里就算沒有讀過源碼的人可能在想了"真的是這樣嗎?" 得出這樣錯(cuò)誤的結(jié)論是因?yàn)楹鲆暳说讓訉?shí)現(xiàn)的隱含條件园骆,導(dǎo)致了縱向邏輯薄弱或者直接導(dǎo)致邏輯錯(cuò)誤舔痪。
實(shí)際ApplicationContext并沒有親力親為,而是委托給了
PathMatchingResourcePatternResolver
锌唾。
b. 把不同性質(zhì)的東西混為一談
這里用分布式里面的經(jīng)吵耄混淆的一個(gè)問題來說明。筆者所在的技術(shù)群,某個(gè)群友說分布式系統(tǒng)就是在CAP下進(jìn)行權(quán)衡巍耗。且不說這句話對(duì)還是不對(duì)秋麸。這句話其實(shí)混淆了分布式系統(tǒng)的概念和分布式系統(tǒng)的問題。導(dǎo)致了這句話不太經(jīng)得起推敲炬太。首先分布式系統(tǒng)的概念是指 若干獨(dú)立計(jì)算機(jī)的集合灸蟆,這些計(jì)算機(jī)對(duì)于"用戶"來說就像是單個(gè)相關(guān)系統(tǒng)。一致性其實(shí)是在分布式系統(tǒng)理論中要解決的問題亲族。那么一致性問題的解決炒考,其實(shí)不僅僅包含著CAP和BASE等基礎(chǔ)理論,還需要考慮從以數(shù)據(jù)為中心的一致性模型和以用戶為中心的一致性模型霎迫。以數(shù)據(jù)為中心的斋枢,還包含了嚴(yán)格一致性、順序一致性等知给。以用戶為中心的包含了單調(diào)讀瓤帚,單調(diào)寫等。
c. 硬套邏輯
在Linux中在用戶空間可以通過nice命令設(shè)置進(jìn)程的靜態(tài)優(yōu)先級(jí)涩赢,這在內(nèi)部會(huì)調(diào)用nice系統(tǒng)調(diào)用戈次。進(jìn)程nice值在-20和+19之間。這是一個(gè)被歷史淹沒的詭異的范圍筒扒。那么根據(jù)這個(gè)怯邪,我就可以說,當(dāng)時(shí)作者的年齡是20花墩,她女朋友年齡19悬秉,所以有了這么一個(gè)詭異的范圍。當(dāng)然我肯定是在胡扯冰蘑,所以和泌,沒有邏輯的時(shí)候就不要硬去套一個(gè)縱向邏輯了。
(3) 橫向邏輯
總分關(guān)系比較好理解懂缕,在源碼閱讀來說其實(shí)就是在區(qū)分模塊(分類)來進(jìn)行閱讀
ApplicationContext
通過繼承如圖的接口來擴(kuò)展自身的功能允跑,那么就可以把這些接口根據(jù)一定規(guī)則進(jìn)行分模塊,分別來進(jìn)行閱讀搪柑,降低整體的閱讀復(fù)雜度聋丝。
(4) 金字塔邏輯
把橫向邏輯和縱向邏輯形成金字塔結(jié)構(gòu)的話,如下圖工碾,我相信至少已經(jīng)達(dá)到了分析閱讀的層次了弱睦。
三、如何解決在閱讀過程中的疑惑
在閱讀過程中肯定會(huì)有疑惑的地方渊额,在這里先說一個(gè)小故事况木,因明是古印度的邏輯學(xué)垒拢,提倡因明立量,最常見的三種量, 現(xiàn)量: 用事實(shí)證明; 比量: 用邏輯推論; 圣言量: 圣人所說火惊。當(dāng)年玄奘西行求法就曾深入學(xué)習(xí)過《因明》求类。因?yàn)槟菚r(shí)候辨經(jīng)幾乎都是賭命的。輸了可能要跳火山口的屹耐。在源碼閱讀的過程中尸疆,也可以理解為是源碼作者在透過源碼,在跟我們對(duì)話惶岭。那么遇到不理解的地方寿弱,或者有疑惑的地方,也可以通過現(xiàn)量的方式按灶,比如某個(gè)方法的調(diào)用邏輯和實(shí)現(xiàn)的功能通過方法名稱無法理解症革。那么就可以直接在往源碼下層去閱讀,甚至直接讀到JDK里面的類鸯旁。比量的話噪矛,其實(shí)就可以利用前面所講到過的兩個(gè)方向的邏輯思維。
當(dāng)然如果通過現(xiàn)量和比量還是無法理解的話铺罢,建議采取逆向思維摩疑,想一想,如果不這么設(shè)計(jì)和實(shí)現(xiàn)畏铆,會(huì)怎么樣^_^
。
還有可能有些實(shí)現(xiàn)細(xì)節(jié)一直在困擾著你吉殃,這時(shí)辞居,除了求人,求百度Google以外蛋勺。建議可以先放下瓦灶,先把整體框架理解了再說。把不理解的地方記錄下來抱完。就算現(xiàn)在不理解贼陶,沒人能解答,可能過幾年經(jīng)驗(yàn)豐富了巧娱,就已經(jīng)有了答案了碉怔。人生的所有問題,總會(huì)有一個(gè)答案的禁添。
四撮胧、閱讀的Spring源碼的切入點(diǎn)
經(jīng)過上面的巴拉巴拉的一大堆,其實(shí)就已經(jīng)隱含了閱讀源碼的切入點(diǎn)ApplicationContext
了老翘。需要先通過橫向邏輯研究對(duì)外提供的功能和內(nèi)部實(shí)現(xiàn)進(jìn)行分類芹啥,然后在每一個(gè)小模塊把實(shí)現(xiàn)原理研究清楚锻离,并且如果模塊功能還是很繁雜,依然可以再次進(jìn)行分類墓怀,直到理解為止汽纠。在每個(gè)模塊內(nèi)部在通過縱向邏輯,把執(zhí)行的流程理清傀履。在這個(gè)過程無論是通過畫圖也好虱朵,還是何種方式,只要能方便記憶和理解的都是好貓啤呼。
五卧秘、結(jié)束語
后續(xù)打算通過前面說的邏輯思維來整理Spring Frameworks的部分源碼。幫助讀者達(dá)到分析閱讀的層次(其實(shí)是在幫我自己官扣,虛榮一把)翅敌。
下篇可能會(huì)先說Resource的相關(guān)實(shí)現(xiàn)內(nèi)容,也有可能去看看K8s的Schedule惕蹄。平時(shí)工作略忙蚯涮,不定期更新,盡量至少一周一篇卖陵。
路還很長遭顶,要想達(dá)到確保系統(tǒng)在整體上能夠從任何未曾預(yù)料到的重創(chuàng)中恢復(fù)過來,還有很長的路要走泪蔫。
參考
https://spring.io/
https://github.com/spring-projects/spring-framework
《機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》
《技術(shù)的本質(zhì)》
《Spring揭秘》
《Spring深度源碼解析》
《如何閱讀一本書》
《行者玄奘》
《代碼整潔之道》
《精準(zhǔn)表達(dá)》
《軟件架構(gòu)》
《架構(gòu)寶典》
《恰如其分的軟件架構(gòu)》
《The Mamba Mentality: How I Play》
《發(fā)布棒旗!設(shè)計(jì)與部署穩(wěn)定的分布式系統(tǒng)》
《分布式系統(tǒng)原理與泛型 第二版》
《持續(xù)演進(jìn)的Cloud Native:云原生架構(gòu)下微服務(wù)最佳實(shí)踐》
《大規(guī)模分布式存儲(chǔ)系統(tǒng):原理解析與架構(gòu)實(shí)戰(zhàn)》
《深入Linux內(nèi)核架構(gòu)》
都是好書, kindle商店買一波