1. 程式人生 > 其它 >【Java學習筆記(一百二十二)】之 Java平臺模組系統詳解

【Java學習筆記(一百二十二)】之 Java平臺模組系統詳解

技術標籤:Java學習筆記# JavaEEjava

本文章由公號【開發小鴿】釋出!歡迎關注!!!


老規矩–妹妹鎮樓:

一. Java平臺模組系統

(一) 概述

封裝是面向物件程式設計的一個特性,類的宣告由公有介面和私有實現構成,類可以通過只修改實現而不影響其使用者的方式得以演化。模組系統使類和包可以有選擇性地獲取,從而可以控制模組的演化。多個現有的Java模組系統都依賴於類載入器來實現類之間的隔離,Java9引入了一個由Java編譯器和虛擬機器支援的新系統,稱為Java平臺模組系統。

(二) 模組

包是類的集合,包提供了一種封裝級別,具有包訪問許可權的所有特性都只能被同一個包中的方法訪問,但是在大型系統中,這種級別的訪問能控制依然不夠,對於所有公有特性可以從任何位置訪問,加入我們想要修改或剔除一個很少使用的特性,那麼我們無從得知有哪些類依賴了這個公有特性。Java平臺模組系統,成功地用於Java API的模組化。一個Java平臺模組包含:

  1. 一個包集合
  2. 可選地包含資原始檔和像本地庫這樣的檔案
  3. 一個有關模組中可訪問的包的列表
  4. 一個有關這個模組依賴的所有其他模組的列表

(三) 模組的命名

模組是包的集合,模組中的包名無需彼此相關,就像路徑名一樣,模組名是由字母,數字,下劃線和句點構成的,而且和路徑名一樣,模組之間是沒有任何層次關係的。當構建供其他人使用的模組時,重要的是保證它的名字是全域性唯一的。命名模組最簡單的方式是按照模組提供的頂級包來命名,如org.slf4j模組,其中的包圍org.slf4j, org.slf4j.spi等等,這個習慣可以防止模組中產生包名衝突。因為任何給定的模組只能放到一個模組之中,如果模組名是唯一的,並且包名以模組名開頭,那麼包名定是唯一的。且模組是沒有作用域的概念的,因此不能在不同的模組中放置兩個具有相同名字的包。

模組名只用於模組宣告中,在Java類的原始檔中,只是使用包名。

(四) 模組化的程式示例

1. 建立包com.hello

在包下建立一個類HelloWorld


2. 建立包含這個包的模組hellomode

需要在模組中新增一個模組宣告,可以將其置於名為module-info.java檔案中,該檔案位於基目錄中(包含com目錄的目錄),按照慣例,基目錄的名字與模組的名字相同。

module-info.java內容:
module hellomode
{}

這個模組宣告之所以為空,是因為該模組沒有任何可以向其他人提供的內容,也不需要依賴任何東西。

3. 編譯模組

將模組宣告和原始的類一起編譯:

javac hellomode/module-info.java hellomode/com/hello/HelloWorld.java

模組宣告檔案最終會以二進位制形式編譯到包含該模組定義的類檔案module-info.class中。

4. 模組化執行程式

為了讓這個程式以模組化應用程式來執行,需要指定模組路徑,與類路徑相似,但是包含的是模組,還需要以模組名/類名的形式指定主類:

java --module-path hellomode –module hellomode/com.hello.HelloWorld

(五) 對模組的需求

模組系統的設計目標之一就是模組需要明確它們的需求,使得虛擬機器可以確保在啟動程式之前所有的需求得到滿足,即模組依賴哪些模組需要在模組宣告中顯示地寫出來,使用requires關鍵詞依賴其他模組,一個模組不能直接或者間接地對自己產生依賴,同時模組也不會自動地將訪問許可權傳遞給其他模組,即依賴是不會有傳遞性的。

module hellomode
{
	requires java.desktop;
}

(六) 模組匯出的包

模組可以使用exports關鍵詞來宣告它的哪些包是可以匯出的,其他的包就被隱藏了,當包被匯出時,它的public和protected的類和介面以及成員在模組外部也是可以訪問的。

module java.xml
{
	exports javax.xml;
	exports javax.xml.catelog;}

(七) 模組和反射

反射可以訪問任何類的私有成員,但是在模組中,這條路是行不通的,如果一個類在模組中,那麼對非公有成員的反射式訪問將會失敗。之前我們通過反射訪問私有域,除非安全管理器不允許對私有域地訪問,有很多使用反射式訪問的庫,如JPA這樣的物件-關係對映器,他們會自動地將物件持久化到資料庫中。如下就是訪問私有域的方式:

Field f = obj.getClass().getDeclaredField(“salary”);
f.setAccessible(true);
double value = f.getDouble(obj);
f.setDouble(obj, value * 1.1);

在模組中,使用opens關鍵詞,開啟指定的包,從而啟動對給定包中的類的所有例項進行反射式訪問,這樣該包中的所有內容就可以被訪問了,但是在執行時只有顯式匯出的包是可訪問的。如下所示:

module hellomode
{
	opens com.hello
}

(八) 自動模組和不具名模組

如果從全新的專案開始,其中所有的程式碼都由我們自己編寫,那麼就可以設計模組,宣告模組依賴關係,並將應用程式打包成模組化的JAR檔案。但是這種情況目前還是達不到的,因為我們的專案肯定要依賴第三方庫,第三方庫很有可能沒有模組化,因此Java平臺模組系統提供了兩種機制解決現在的情況:自動化模組和不具名模組。


1. 自動化模組

模組路徑上美哦與module-info.class檔案的JAR被稱為自動模組,自動模組的屬性為:

(1) 隱式地包含對其他所有模組的requires子句;

(2) 其所有包都被匯出,且是開放的;

(3) 如果在JAR檔案清單META-INF/MANIFEST.MF中具有鍵為Automatic-Module-Name的項,那麼它的值會變為模組名;

(4) 否則,模組名將從JAR檔名中獲得,將檔名中尾部的版本號刪除


2. 不具名模組

任何不在模組路徑中的類都是不具名模組的一部分,和自動模組一樣,不具名模組可以訪問所有其他的模組,它的所有包都會被匯出,並且都是開放的。但是沒有任何明確模組(非自動模組,非不具名模組)可以訪問不具名模組。

(九) 服務載入

在模組系統之前,使用ServiceLoader類將服務介面與實現匹配起來,而在Java平臺模組系統中這種機制將會更加簡單。服務消費者必須基於其認為合適的標準在所有的實現中選擇一個,過去是通過將文字檔案放置到包含實現類的JAR檔案的META-INF/services目錄中提供給服務消費者的,模組系統直接新增語句到模組描述符中。提供服務實現的模組可以新增一條provides語句,它列出了服務介面以及實現類,如下所示:

module jdk.security.auth
{
	provides javax.security.auth.api.LoginModule with
		com.sun.security.auth.module.UnixLoginModule,}

這與META-INF/services檔案是等價的,而使用它的消費模組包含一條uses語句:

module java.base
{
	uses javax.security.auth.spi.LoginModule;
}

當消費模組中的程式碼呼叫ServiceLoader.load(ServiceInterface.class)時,匹配的提供者類將被載入。