JAVA9模塊化詳解(二)——模塊的使用


JAVA9模塊化詳解(二)——模塊的使用

二、模塊的使用

  各自的模塊可以在模塊工件中定義,要么就是在編譯期或者運行期嵌入的環境中。為了提供可靠的配置和強健的封裝性,在分塊的模塊系統中利用他們,必須確定它們的位置,然后決定他們如何關聯彼此。

2.1 模塊的路徑

  為了在確定在工件中定義的模塊的位置,模塊系統搜索模塊的路徑,它在主系統中定義。模塊路徑是一個序列,它的每一個元素要么是一個模塊工件,要么是一個包含模塊工件的目錄。模塊路徑中的元素被第一個工件有序的搜索,這個工件定義了一個合適的模塊。模塊的路徑在物理上不同於類路徑,而且更強大。類路徑天生的脆弱性是它定位了路徑下所有工件中的個體類型,在工件中間沒有任何的區分。這使得它在工件丟失時不可能提前告知,它也允許不同的工件在相同的包中定義類型,即使那些工件代表中相同程序組件的不同版本,或者完全不同的組件。

  相比之下,模塊路徑定位整個的模塊,而不是個體類型。如果模塊系統不能從模塊路徑中處理工件的特殊依賴,或者如果它在相同的目錄下遇到了兩個模塊名字相同的工件,這是編譯器或者虛擬機將報告一個錯誤並退出。

  嵌入到編譯器或運行期環境的模塊,連同模塊路徑下工件定義的模塊被交付到全局的可被觀察的模塊。

2.2 解決方案

  假設我們有一個應用,它使用了上一章講到的com.foo.bar模塊,也用到了java.sql模塊,包含了應用核心的模塊聲明如下:

module com.foo.app {
    requires com.foo.bar;
    requires java.sql;
}

  給出的這個最初的應用模塊,模塊系統解決了requires項表達的依賴,它通過定位額外可觀察到的模塊完成了依賴的任務,然后解決那些模塊的依賴,等等,直到每一個模塊的每一個依賴都解決完。這個傳遞閉包計算的結果是一個模塊圖,它包含了從第一個模塊到第二個模塊的矢量,依賴的每一個模塊通過一些其他的模塊解決。

  為了構建com.foo.bar模塊的模塊圖,模塊系統檢測到了java.sql的模塊聲明,如下:

module java.sql {
    requires java.logging;
    requires java.xml;
    exports java.sql;
    exports javax.sql;
    exports javax.transaction.xa;
}

  它也檢測到了com.foo.bar的模塊聲明,在上面已經展示過,同時也檢測到了org.baz.qux,java.logging,和java.xml模塊,為了簡潔,后三個模塊在這里不做展示,它們也沒有聲明依賴其他模塊。基於這些模塊的聲明,com.foo.app模塊計算出的模塊圖包含如下的節點和邊界:

    

  在上面的圖中,深藍色的線代表着確定的依賴關系,它在requires項中定義,淺藍色的線代表着隱性的依賴,每一個模塊都依賴基礎模塊(base module)。

 2.3 可讀性

  在模塊圖中,當一個模塊直接依賴另一個時,第一個模塊中的代碼可以訪問第二個模塊中的類型。因此,我們說第一個模塊讀取第二個模塊,相等的,也可以說第二個模塊對於第一個模塊是可讀的。於是,上面的圖中,com.foo.app模塊讀取com.foo.bar和java.sql,但是不讀取org.baz.qux,java.xml和java.logging。java.logging模塊對於java.sql模塊是可讀的,對於其他模塊不可讀。

  在模塊圖中可讀性定義的關系是可靠性配置的基礎:模塊系統確保每一個依賴都被確定的其他的模塊解決,模塊圖是非循環的,每一個模塊最多讀取一個模塊定義的包(package),定義了相同名字的模塊彼此間互不干擾。

  可靠性配置不僅僅是更可靠,它也更快。當一個模塊中的代碼涉及到一個包(package)中的類型時,這個包肯定被定義在這個模塊中,或者這個模塊確切讀取的其他模塊中。因此,當尋找確切類型的定義時,不需要在多個模塊中尋找,也不需要更糟的在類路徑下尋找。

2.4 可接入性

  模塊中定義的可讀性關系,結合了模塊聲明中的exports項,是強健的封裝性的基礎:java編譯器和虛擬機認為,只有當第一個模塊被其他模塊讀取時,第一個模塊包中的公共類型才能被其他包訪問,按照這個意思,第一個模塊輸出了可訪問的包。如果S和T兩個類型定義在不同的模塊中,T是公共的(public),S中的代碼可以訪問T的要求如下:

  1.   S模塊讀取(requires)T模塊;
  2.   T模塊輸出(exports)T包; 

  一個類型用這種方式引用了不可訪問的模塊邊界是不可用的,比如它訪問的私有的方法或變量是不可用的。任何使用它的嘗試都會引起編譯器報告錯誤,或者被虛擬機拋出一個IllegalAccessError的錯誤,或者被運行期的反射API拋出IllegalAccessException 的錯誤。因此,一個類型即使是public,但是它並沒有在模塊聲明中輸出(exports),它也只能在自己的模塊中被訪問。

  如果一個模塊的封裝類型是可以訪問的或者成員的聲明是可以訪問的,則通過模塊邊界引用的方法和字段也是可以訪問的。

  為了看到強大的封裝性是如何工作的,我們在上面的模塊圖中,添加了標注

  

  com.foo.app模塊中的代碼可以訪問com.foo.bar.alpha包中的公共類型,英文com.foo.app依賴,也可以說讀取com.foo.bar模塊,並且英文com.foo.bar輸出com.foo.bar.alpha包。如果com.foo.bar包含了一個內部的包com.foo.bar.internal,並且com.foo.bar不輸出它,則com.foo.app不能訪問這個包中的任何類型。com.foo.app中的代碼不能涉及org.baz.qux中的類型,英文com.foo.app不依賴org.baz.qux。

2.5 隱性可讀性

  如果一個模塊讀取另一個,在某種情況下,它在邏輯上也讀取其他的一些模塊。舉個例子,平台模塊java.sql依賴java.logging和java.xml模塊,它不僅使用了那些模塊中的類型實現了代碼,並且還定義了那些模塊中的類型。比較特殊的,java.sql.Driver接口定義了公共方法:

public Logger getParentLogger();

  在這里,Logger是java.logging模塊中java.util.logging包輸出的類型。假如com.foo.app模塊引用了這個方法,並且打印了日志:

String url = ...;
Properties props = ...;
Driver d = DriverManager.getDriver(url);
Connection c = d.connect(url, props);
d.getParentLogger().info("Connection acquired");

  如果com.foo.app模塊像上面那樣聲明,它是不會工作的。getParentLogger方法返回了一個Logger,它在java.logging模塊中聲明,它不被com.foo.app模塊讀取,所以Logger類中的info方法在編譯期和運行期都是失敗的,因為那個類是不能被訪問的。

  解決這個問題的一個方法是希望每一個模塊的作者,在依賴了java.sql模塊並且使用Logger類時,在聲明一個java.logging的依賴。這種方法是不可靠的,它違反了最少意外的原則:如果一個模塊依賴了第二個模塊,它非常自然的希望,第一個模塊需要使用的類型,即使這個類型定義在第二個模塊中,其他模塊僅僅依賴第一個模塊就可以直接訪問。

  於是,我們擴展了模塊聲明,一個模塊可以把可讀性授權給另外的,依賴它的模塊,擴展可讀性的表達式(public)在requires項上,如下所示:

module java.sql {
    requires public java.logging;
    requires public java.xml;
    exports java.sql;
    exports javax.sql;
    exports javax.transaction.xa;
}

  public修飾語的意思是任何依賴了java.sql模塊的模塊,不僅可以讀取java.sql模塊,還可以讀取java.logging和java.xml模塊。com.foo.app的模塊圖增加了另外兩個深藍色的線,他們被綠線連接到了java.sql模塊。如圖:

  現在com.foo.app模塊可以讀取java.xml和java.logging模塊中的所有公共類型,雖然它的聲明中並沒有提到那些模塊。

  總的來說,如果一個模塊輸出一個包,這個包包含了第二個模塊中的包的類型,則第一個模塊應該聲明為 requires public ,依賴第二個模塊。這可以保證其他依賴了第一個模塊的模塊可以自動讀取第二個模塊,因此,可以訪問那個模塊輸出包中的所有公共類型。

 

  至此,JAVA9模塊化詳解(二)——模塊的使用就先介紹到這里,這個系列還會繼續,請大家多多支持,有不妥之處,還請大家多多交流。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM