關於Java 7模塊系統

火星人 @ 2014-03-10 , reply:0


最近,新的Java模塊系統已經受到了大量的關注.在觀看過Devoxx關於Jigsaw的一段演示后,我很興奮,覺得它應該會是針對複雜類路徑版本問題和JAR陷阱等問題的解決方案.開發者最終能夠使用他們所期望的任何Xalan版本,而無需被迫使用授權機制.不幸的是,通往更加有效的模塊系統的征途並不是很清晰.

  在研究確實問題之前,我們先來看一些基本概念:

  模塊化

  模塊化是解決複雜性問題很重要的工具.把應用分成不同的部分(模塊、庫、包、子項目和組件),再分別進行計算,是行之有效的方式.模塊化的最終目的是能定義出一套API用於模塊間的溝通.

  如果模塊間所有的通訊都只通過這種API來實現,那麼模塊是松耦合的,於是:

  改變某個模塊的實現會很容易

  開發和測試各個模塊能很容易獨立開來

  面向對象模式也是類似的道理.在OOP中,理想的狀況是擁有大量小的、可重用的、簡單並分離良好的對象.在模塊系統中,就可以完美地實現小的、可重用的、簡單並分離良好的模塊.它們的想法和最初的動機是完全一樣的,只是規模有所不同.

  邏輯分離

  傳統上,Java中有兩種辦法來實現模塊化.邏輯分離是最自然的方式.它包括將應用程序分割成邏輯模塊(子項目),最后再部署成一個完整的應用.通過定義正確的包來實現邏輯分離也是可能的,但更通用的辦法是把應用分割成一些存檔文件(也就是JAR包).邏輯分離里能促進模塊的重用,有助實現模塊間的松耦合.你甚至有可能定義一個API,然後宣布所有模塊間的通訊都要通過這個給定的API來實現.這樣的想法有個大問題,那就是你很難強破大家都採用這種限制性用法,沒有任何一種機制能夠確保這個API的用法.你也沒法把那些應用通過給定模塊來使用的類和作為公共API一部分的類區分開來.如果一個類是「 公共的」,那它就可以被任何其他類使用,無論調用它的那個類屬於哪個模塊.另一方面,受保護的或者包級可見性的類在其模塊內部的調用也有限制.通常來說,涵蓋了一些包以及包中類的模塊需要能夠互相調用.因此即使某個應用是由一些邏輯模塊組成,但如果這些模塊是耦合的,那麼分離也根本沒有用處.

  物理分離

  另外一個傳統的辦法就是物理上的分離.你可以通過將應用分割成不同的組件,然後把每個組件部署到不同的JVM上而實現分離.這些組件間通過遠程訪問機制進行通訊,比如RMI、CORBA或者WebServices.物理分離實現了分離,也實現了松耦合,但負面影響是開支很大.為實現分離而專門採用遠程訪問機制,有點殺雞用牛刀的味道.這會增加開發和部署不必要的複雜性,性能上所受到的影響也不能忽視的.

模塊系統

  模塊系統的作用位於邏輯分離和物理分離之間.它強調模塊分離,但各個模塊仍然部署到同一個JVM中,模塊間的通訊由簡單傳統的方法調用組成,因此不會有運行時的開支負擔.在Java生態系統中最流行的模塊框架是OSGi.它是一個成熟的規範,具有幾個不同的實現.在OSGi中,模塊被稱作bundle,每個bundle等同於一個JAR.每個bundle也包含一個 META-INF/MANIFEST.MF文件,這個文件會宣布導出哪些包(package)以及導入哪些包.只有那些導出包中的類才能被其他 bundle所使用,而其他包都只面向包的內部成員,包里的類也只能在自身bundle中使用.

  比如下面這個聲明:

  Manifest-Version: 1.0

  Import-Package: net.krecan.spring.osgi.common

  Export-Package: net.krecan.spring.osgi.dao

  Bundle-Version: 1.0.0

  Bundle-Name: demo-spring-osgi-dao

  Bundle-SymbolicName: net.krecan.spring-osgi.demo-spring-osgi-dao

  這個規範指定了名叫demo-spring-osgi-dao的bundle,它要導入包名為 net.krecan.spring.osgi.common中的類,並導出包名為net.krecan.spring.osgi.dao中的類.換句話說,這段聲明表明其他模塊只能使用net.krecan.spring.osgi.dao包.相反地,這個模塊要使用的則只是 net.krecan.spring.osgi.common包,也可能會由OSGi來提供專門的模塊負責導出這個包.當然你完全可以在導入和導出聲明中定義多個包名.

  需要特別注意的是,OSGi的模塊化是構建在Java之上的.它不是語言的一部分!這裡的模塊分離雖然可以由GUI來執行,但不可以由編譯器來執行.運行基於OSGi的應用時,你會需要一個OSGi的容器.這個容器可能是運行時環境的一部分,比如在Spring DM伺服器 style="COLOR: #000000" href="http://server.it168.com/" target=_blank>伺服器中,也可能是嵌入在應用程序中的.這個容器不僅負責提供分離,也提供了其他諸如安全 style="COLOR: #000000" href="http://safe.it168.com/" target=_blank>安全、模塊管理和生命周期管理之類的服務.OSGi還提供大量其他有趣的特性,但這些並不是本文所要關注的.

  關於JSR-277曾經有很多爭議,這個JSR一度跟OSGi有部分重複.連續好幾個月,雙方的專家都極力辯論誰更優秀,直到JSR-277被宣布放棄,而新的模塊系統將會是Java 7的一部分.

  JSR-294

  這個新的模塊系統的第一部分就是JSR-294,即所謂的超級包.也正是這個規範闡釋了Java語言的模塊部分的概念.

  JSR-294引入了新的可見性關鍵字「module」.如果一個成員擁有這樣的可見性,那就意味著它只對同一模塊中的成員可見.它會創建一個內部的 API,只有模塊本身能調用.就此看來,「public」關鍵字應當只在聲明一個公共的API時才用.而在其他情況下,應當使用「module」或者有更多限制的可見性關鍵字.當然,一旦語言中有了「module」關鍵字,那麼模塊之間的可見性限制將會由編譯器來負責檢查.

  JSR-294也允許定義依賴性.你可以在某個給定版本中,定義某個模塊依賴於另一模塊.比如:

  //org/netbeans/core/module-info.java

  @Version("7.0")

  @ImportModule(name="java.se.core", version="1.7 ")

  module org.netbeans.core;

  最后一句表明「org.netbeans.core」模塊依賴「java.se.core」的1.7版本或者更高.這類似於Maven的依賴性或者 OSGi的導入.你也可以暫時不要管這些語法,將來語法可能會另有變化.重要的是,這兒的依賴是在module-info.java中定義的,會被編譯成class文件.而OSGi中,依賴則是在普通的文本文件中定義的.

Jigsaw項目

  Jigsaw項目是這個新模塊系統的第二部分.我預計它會是JSR-294特定於Sun的實現,也會是Sun JDK的模塊化實現.既然創建完整的JDK模塊化是有必要的,Sun就希望把標準庫分裝成模塊.這直接簡化了JRE中的內容整合.整個JRE除了Swing之外的所有內容因此都能夠在移動設備上運行.它還有可能為語言引入新的標準API,而無需再等待整個平台的新版本發布.目前看起來,這個項目絕對有希望實現.

  但我對此還有個擔憂,那就是,a href="http://blogs.sun.com/mr/entry/jigsaw">專有的Jigsaw和JSR標準之間的關係並不清晰,正如Mark Reinhold所說的:

  對Jigsaw的投入無疑會創建出一個簡單的、低層次的模塊系統,它的設計會嚴格地朝著JDK模塊化的目標而發展.開發人員可以把這個模塊系統運用到他們的代碼中去,Sun對這個模塊系統也會是絕對的支持,但它不會是Java SE 7平台規範的官方部分,也可能不會被其他SE 7實現所支持.

  這段話說的不是很清楚,當中有很多疑問.他的意思是說創建的模塊只能在Sun JRE中運行嗎?還是想說,如果開發者寫了「@ImportModule(name="java.se.core", version="1.7 ")」,那麼這個模塊只能在Sun JRE中運行,而不能在IBM JRE環境中運行嗎?或者他的意思是不是說Sun會以某種方式把它的JRE分割成許多模塊,而Oracle會選擇另外的方式去分割嗎?(譯者註:至少現在看來,不太會有這樣的可能了,Oracle剛剛收購了Sun).我們希望都不是,還有「編寫一次,到處運行」的原則.

  細究起來問題更多.我們並不清楚Jigsaw項目的主要目標是什麼.據項目本身所宣布的主要目標來看,它要實現的是Sun JRE的模塊化,但如果純粹是要實現模塊化的話,就不需要對語言做任何改變.Sun可以對JRE進行模塊化,而不修改Java語言本身.

  這些語言上的變化會不會成為Sun JRE模塊化帶來的副產品?如果是,那就徹底錯了!語言變化是一等公民,而不是專屬的副產品.

  依賴

  我的另外一個擔心在於依賴性.如果依賴性由模塊系統來管理,那就不再需要classpath了.一方面這很不錯,classpath經常會導致所謂的JAR地獄問題.但另一方面,classpath也是極度靈活的,我恐怕這種靈活性是不可能由一個靜態的模塊依賴能夠擁有的.讓我們來看看為什麼:

  部署時依賴

  Java中有兩種類路徑(classpath).一個是構建路徑(buildpath),它用在構建時.另外一個是類路徑,用在運行時.兩者幾乎相同,但又不完全是.經典的例子就是JDBC驅動.在構建時,你不需要指定JDBC驅動,JDBC介面是Java核心庫的一部分.但在運行時,你就有必要確保類路徑中有JDBC的驅動.如果某個編程人員需要修改資料庫連接,他只需要在配置文件中修改驅動類的名稱,並把驅動jar文件添加到類路徑就可以了.如果所有的依賴需要在編譯時指定,開發者很明顯無法做到這點.當然,在Java EE中,他可以使用JNDI數據源,但在Java SE中沒有類似的東西,一旦修改JDBC驅動,就不得不重新編譯整個應用,這明顯很不靠譜.

  通常來說,重新編譯不太可能.在一些組織中,最終的應用是由所謂的應用裝配器的模塊組裝而成的.開發者沒有源代碼,他只是把一些JAR放在一起,修改一下配置文件,然後創建最終的包.應用裝配器的角色甚至在Java EE的規範中都有提到.

  可選依賴

  類似的問題就是可選依賴.我們假設我們要做一個像log4j這樣的日誌框架.這個庫可以對JMS記錄日誌,因此JMS包涵蓋在構建路徑中.但99%的用戶並不使用JMS日誌,因此他們不需要把依賴放在類路徑中.對於這樣的問題,要有某種機制來解決.我們需要一個庫來構建這個模塊,這種依賴對最終用戶來說則是可選的.當然,完美的情況是,JMS功能會是個獨立模塊,可我們並不是生活在一個完美的世界,某些時候用這種方式來分割項目也不太現實.

  依賴衝突

  另外一個大問題就是依賴衝突.如果你用過Maven,就不難理解我在說什麼了.大多數企業應用都會用到大約十幾個第三方庫,它們之間的互相依賴有時就會發生衝突.比如,一個開發者想要使用Hibernate,它依賴commons-collections 2.1.1,他還想用commons-dbcp,卻需要依賴commons-collections 2.1.開發者自己或者應用裝配器需要決定怎樣解決此類問題.他要麼決定在應用中只用某個特定版本的庫,要麼決定在應用的不同部分採用不同版本的類庫.重要的是,這些問題無法自行解決.它總需要由某個了解各個模塊在應用中如何運作的人來作決定,而這個人又要能識別不同版本間可能存在的不兼容性.

  關於Java依賴性,還有許多東西本文不展開討論,但需要銘記的一點是,它們不是靜態的.一個應用的構件可能採用了某套類庫,而它的運行卻需要另外一套完全不同的庫.所有模塊系統以某種方式把這些問題解決掉.Maven具有大量關於如何配置依賴,以及如何處理依賴衝突等等的選項,但它只是個構建系統.最糟糕的情況是需要手動配置類路徑.OSGi則是另外一種情形.它只處理運行時(部署時)依賴,不管構建時.新的Java模塊系統會同時支持構建時和運行時依賴(我猜測),甚至會把既有的複雜問題變得愈加複雜.

  總結

  當然,我相信Sun的工程師並不想要破壞Java本身.我想他們也是為了讓Java變得更好、更易於使用,但我擔心政治和市場因素會遠大於技術影響.再次聲明,這不會僅僅是個API的變化或者是特定於Sun的變化.這會是語言級別的變化!一旦語言被改變了,一旦添加了「module」關鍵字,就不會再有回頭路.到那時,Java中會有個模塊系統,無論喜不喜歡,我們都非得要用到這個模塊系統.真得很難想象帶模塊化的JVM,也很難想像Java語言中會有個 「module」關鍵字,而我們還要在這之上使用OSGi.





[火星人 via ] 關於Java 7模塊系統已經有201次圍觀

http://www.coctec.com/docs/java/show-post-61662.html