Nicolai Parlog是一位熱情的軟件工程師,數字版權與開源軟件的狂熱擁護者;他對AssertJ、ControlsFX、FindBugs及Property Alliance等項目都做出過重要的貢獻。近日,Parlog就Jigsaw項目撰寫了一篇文章,談到了Jigsaw項目的一些不足以及改進之處。Jigsaw項目有著雄心勃勃的宏偉目標,其目標之一就是徹底擺脫極易出錯且問題多多的類路徑機制中的JAR地獄問題。不過,雖然該項目的其他目標會在不久的將來得以實現,但解決JAR地獄問題這一目標似乎並不是那麼容易的。
為了更好地理解我們接下來要討論的內容,首先來看一下JAR地獄問題,接下來介紹Jigsaw項目將會解決問題的哪些方面,以及為什麼說Jigsaw所嘗試解決的問題並不會對整個問題域產生本質的影響。最後,我們來看一下官方對於這個話題的立場,並給出如何防止出現模塊地獄的提案。
JAR地獄問題
JAR地獄存在著如下循環問題:
根據構建工具與組件系統(JDK開發者稱之為容器)為我們所帶來的諸多功能與特性,我們可以認為表述不清以及傳遞性依賴問題已經在很大程度上得到了解決,遮蔽問題至少得到了緩解,而復雜的類加載也不再是老生常談的問題了。這樣,版本沖突就成為JAR地獄中最為嚴重的一個問題了,它影響到了很多很多項目每天的更新決策。
Jigsaw將會帶來哪些改變?
我之前曾就Jigsaw項目會為Java 9帶來哪些新特性專門寫過文章進行過介紹,不過這裡將從不同的視角進行闡述。首先,它會受到當前的早期訪問構建版的影響;其次,我們這裡只從與JAR/模塊地獄相關的角度進行介紹。
Jigsaw為Java帶來的核心概念就是模塊化。簡而言之,模塊就像JAR一樣,同時帶有一些附加信息與特性。這些信息包含了模塊的名字以及模塊所依賴的其他模塊的名字。
依賴
當編譯器與JVM在處理模塊時,他們會解析這些信息。在編譯或啟動時,他們會通過模塊路徑傳遞性解析所有依賴。總體來說,這類似於類路徑掃描,不過現在尋找的是整個模塊而非單個類,對於JVM來說,這是在啟動期而非運行期進行的。如果在模塊路徑上無法找到所有依賴,那麼解析模塊的傳遞性依賴就會失敗。這顯然可以解決表述不清,以及無休止的傳遞性依賴的問題。我認為這是個很棒的做法,Java語言現在正式知道關於依賴的信息了,所有工具(編譯器與JVM等)都能理解這一點並正常使用!不過,我認為這並不會對開發者每天的工作產生多少積極的影響,因為現在很多既有的基礎設施都已經解決這個問題了,比如說構建工具等。
遮蔽
Jigsaw消除了遮蔽的問題。模塊系統可以確保每個依賴都會被另一個模塊所實現,每個模塊都會讀取至多一個模塊,定義了同名包的模塊之間並不會相互干擾。更准確地說,模塊系統在遇到模糊不清的情況時就會終止並報錯,比如說兩個模塊將相同的包導出到相同模塊中。
版本沖突
我們認為第三方庫的版本沖突是JAR地獄最為難以解決的問題。最直接的解決方案就是一個模塊系統能夠加載同一個模塊的不同版本。這需要確保這些版本之間不存在互相交互的情況。問題在於:在單個配置中,沒必要支持一個模塊的多個版本。實際上,當前的構建既不會創建,也無法理解模塊版本信息。曾有人使用了一些變通辦法。最丑陋,同時也是最可行的辦法就是重命名出現沖突的構件,這樣他們就不再是相同模塊的兩個不同版本了,而是兩個完全不同的模塊。不過,這種做法最後證明也是行不通的。顯然,確保“定義了同名包的模塊之間不會相互干擾”是在兩個模塊導出相同包時拒絕任何啟動配置來實現的。即便沒有模塊讀取他們亦如此!
復雜的類加載
模塊與類加載器之間如何交互以及如何改變類加載的復雜性是個很棘手的問題。實際上,模塊系統對模塊與類加載器之間的關系並沒有做多少限制。類加載器可以從一個模塊或是多個模塊來加載類型,只要模塊之間不存在相互干擾的情況,並且每個模塊中的類型只由一個加載器加載即可。因此,類加載器與模塊之間是一對多的關系。
模塊地獄?
既然依賴與遮蔽問題已經得到了解決,並且類加載問題也得到了改進,那我為何還要討論模塊地獄呢?就是因為版本沖突麼?沒錯!如果Jigsaw想要解決JAR地獄問題,它就需要特別注意版本沖突問題。否則,很多項目並不會出現什麼起色。他們依然要面對版本沖突問題,並且會陷入到自定義類加載器的夢魇中。
提案
我的提案是讓開發者與構建工具能夠傳遞一些額外的信息,這些信息能夠解決一些含糊不清的問題。傳遞這種信息的兩種常見方式是命令行與配置文件。如果使用命令行參數,那麼每次啟動時都需要輸入一次。根據信息的多少以及項目的規模,這種做法可能會變得非常乏味。可以通過構建工具來創建配置文件,然後再通過命令行指定配置文件。這看起來是個不錯的解決方案。目前,初始模塊與所有的傳遞性依賴都是通過單個配置來解析的,這形成了單獨的一個層次。不過,我們可以在運行期將相同模塊的多個版本加載到不同層次中,這正是組件系統要做的事情。總的來說,我的建議就是通過多個層次來顯式指定配置。