Java SE 9 模組化示例
Java SE 9 模組化示例
作者:Grey
原文地址:Java SE 9 模組化示例
說明
Java SE 9
引入了模組系統,模組就是程式碼和數據的封裝體。模組的程式碼被組織成多個包,每個包中包含Java類和介面;模組的數據則包括資源文件和其他靜態資訊。
在module-info.java
文件中,我們可以用新的關鍵詞module
來聲明一個模組。
Java 9將JDK劃分為多個模組以支援各種配置。參考JEP 200: The Modular JDK
可以使用如下命令查詢所有的模組(註:JDK 版本大於等於9)
java --list-modules
模組描述符是在名為module-info.java
的文件中定義的模組聲明的編譯版本。每個模組聲明都以關鍵字module
開頭,後跟唯一的模組名稱和括在大括弧中的模組正文,格式如下
module modulename {
}
模組聲明的主體可以是空的,也可以包含各種模組指令(比如:exports
, module
, open
, opens
, provides
, requires
, uses
, with
等)。
下面介紹各種指令的含義
require 指令指定此模組依賴於另一個模組,這種關係稱為模組依賴項。每個模組必須顯式聲明其依賴項。當模組A需要模組B時,模組A稱為讀取模組B,模組B由模組A讀取。要指定對另一個模組的依賴關係,請使用 require,格式如下
requires modulename;
require static 指令,用於指示在編譯時需要模組,但在運行時是可選的。這稱為可選依賴項。
requires transitive 表示模組的隱含可讀性,
如果這樣寫
module a {
requires b;
}
表示
-
a模組強制要求存在b模組,這稱為可靠配置。
-
a模組可以讀入b模組的文件,這稱為可讀性。
-
a模組可以訪問b模組的程式碼,這稱為可訪問性。
但是如果寫成
module a {
requires transitive b;
}
表示:
如果其他模組比如c依賴於a模組,a模組使用requires transitive
引用b模組,那麼c模組即便沒有直接聲明依賴於b模組,它對b模組也是具有可讀性的,這個稱之為隱含可讀性。
即:a requires transitive b
加上c requires a
,則c對b也是可讀。如果去掉transitive
,則c對b不可讀。
exports 指令指定模組的一個包,其public
類型(及其嵌套的public
類型和protected
類型)應可供所有其他模組中的程式碼訪問。
exports…to 指令使您能夠在逗號分隔的列表中精確指定哪些模組或模組的程式碼可以訪問導出的包,這稱為限定導出。
uses 指令指定此模組使用的服務,使該模組成為服務使用者。服務是實現介面或擴展uses
指令中指定的抽象類的類的對象。
provides…with 指令指定模組提供服務實現,使模組成為服務提供者。指令的 provide 部分指定列出的介面或抽象類,而 with 部分指令指定實現介面或擴展抽象類的服務提供程式類的名稱。
open , opens, 和 opens…to 指令用於了解包中的所有類型以及某個類型的所有成員(甚至是其私有成員),無論您是否允許此功能。在 JDK 9 之前,這一功能是通過反射來實現的。
opens 指令
opens package
允許僅運行時訪問包(package)。
opens…to 指令
opens package to comma-separated-list-of-modules
允許特定模組(module)僅運行時訪問包(package)。
open 指令
open module modulename {
// module directives
}
允許僅運行時訪問模組中的所有包。
環境準備
本地的 JDK 版本應該大於等於 JDK 9。
使用示例
本文的示例均參考於:Project Jigsaw: Module System Quick-Start Guide
註:以下示例均在Windows
下進行,在Mac
或者Linux
下把\\
換成/
。javac
的版本一定要大於等於JDK 9
。
示例1
新建一個example-01
文件夾,作為項目根目錄,在項目根目錄下創建一個src
文件夾,在這個文件夾下,創建一個文件夾作為module
,文件夾名稱為:com.greetings
。在com.greetings
文件夾下新建module-info.java
文件夾,同時,在com.greetings
文件夾中新建包,
即在com.greetings
文件夾下新建com
文件夾,在com
文件夾下新建greetings
文件夾。在greetings
文件夾下新建Main.java
。
目錄結構如下
- example-01
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
module-info.java
文件中內容如下
module com.greetings {
}
Main.java
中內容如下
package com.greetings;
public class Main {
public static void main(String[] args) {
System.out.println("Greetings!");
}
}
在example-01
目錄下新建mods
文件夾,用於存放編譯後的目錄,在mods
文件夾中新建一個com.greetings
的目錄,
目錄結構變成了如下
- example-01
- mods
- com.greetings
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
在example-01
目錄下執行如下編譯命令,
javac -d mods\\com.greetings src\\com.greetings\\module-info.java src\\com.greetings\\com\\greetings\\Main.java
編譯完畢後,項目目錄如下
- example-01
- mods
- com.greetings
- module-info.class
- com
- greetings
- Main.class
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
接下來運行java
命令進行執行
java --module-path mods -m com.greetings/com.greetings.Main
控制台輸出結果
Greetings!
--module-path
是模組路徑,其值是一個或多個包含模組的目錄。-m
選項指定的是主模組,斜線後的值是模組中主類的類全名。
示例2
如上例,我們準備好文件夾目錄和相關文件
- example-02
- mods
- com.greetings
- org.astro
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
- org.astro
- module-info.java
- org
- astro
- World.java
其中src\\org.astro\\module-info.java
內容如下
module org.astro {
exports org.astro;
}
src\\org.astro\\org\\astro\\World.java
內容如下
package org.astro;
public class World {
public static String name() {
return "world";
}
}
src\\com.greetings\\module-info.java
module com.greetings {
requires org.astro;
}
`src\com.greetings\com\greetings\Main.java
package com.greetings;
import org.astro.World;
public class Main {
public static void main(String[] args) {
System.out.format("Greetings %s!%n", World.name());
}
}
然後,在example-02
下分別對org.astro
和com.greetings
兩個模組進行編譯。
javac -d mods\\org.astro src\\org.astro\\module-info.java src\\org.astro\\org\\astro\\World.java
javac --module-path mods -d mods\\com.greetings src\\com.greetings\\module-info.java src\\com.greetings\\com\\greetings\\Main.java
編譯完成後,項目目錄如下
- example-02
- mods
- com.greetings
- module-info.class
- com
- greetings
- Main.class
- org.astro
- module-info.class
- org
- astro
- World.class
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
- org.astro
- module-info.java
- org
- astro
- World.java
然後運行
java --module-path mods -m com.greetings/com.greetings.Main
控制台列印
Greetings world!
示例3
本示例演示如何打包,新建example-03
文件夾,把示例2的所有程式碼和目錄都原封不動拷貝進去。
- example-03
- mods
- com.greetings
- org.astro
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
- org.astro
- module-info.java
- org
- astro
- World.java
在example-03
文件夾中新建一個mlib
的文件夾,用於存放打包後的文件。
- example-03
- mlib
- mods
- com.greetings
- org.astro
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
- org.astro
- module-info.java
- org
- astro
- World.java
執行打包命令,根據依賴關係,應該先打org.astro
的包。
jar --create --file=mlib\\[email protected] --module-version=1.0 -C mods\\org.astro .
然後再打com.greetings
的包
jar --create --file=mlib\\com.greetings.jar --main-class=com.greetings.Main -C mods\\com.greetings .
打包完成後,目錄結構如下
- example-03
- mlib
- com.greetings.jar
- [email protected]
- mods
- com.greetings
- org.astro
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
- org.astro
- module-info.java
- org
- astro
- World.java
接下來執行jar
包
java -p mlib -m com.greetings
控制台列印
Greetings world!
jar
工具有許多新的選項(見jar -help),其中之一是列印作為模組化JAR打包的模組的模組聲明。
jar --describe-module --file=mlib/[email protected]
控制台輸出
[email protected] jar:file:///C:/workspace/hello-module/example-03/mlib/[email protected]!/module-info.class
exports org.astro
requires java.base mandated
示例4
服務允許服務消費者模組和服務提供者模組之間進行鬆散耦合。
這個例子有一個服務消費者模組和一個服務提供者模組。
模組com.socket
輸出了一個網路套接字的API
。該API
在包com.socket
中,所以這個包被導出了。該API
是可插拔的,允許替代的實現。服務類型是同一模組中的com.socket.spi.NetworkSocketProvider
類,因此包com.socket.spi
也是導出的。
模組org.fastsocket
是一個服務提供者模組。它提供了com.socket.spi.NetworkSocketProvider
的一個實現。它沒有輸出任何包。
創建example-04
文件夾和相關目錄
- example-04
- mods
- com.socket
- com.greetings
- org.fastsocket
- src
- com.socket
- module-info.java
- com
- socket
- spi
- NetworkSocketProvider.java
- NetworkSocket.java
- com.greetings
- module-info.java
- com
- greetings
- Main.java
- org.fastsocket
- module-info.java
- org
- fastsocket
- FastNetworkSocketProvider
- FastNetworkSocket.java
其中src\\com.socket\\module-info.java
module com.socket {
exports com.socket;
exports com.socket.spi;
uses com.socket.spi.NetworkSocketProvider;
}
src\\com.socket\\com\\socket\\NetworkSocket.java
程式碼如下
package com.socket;
import java.io.Closeable;
import java.util.Iterator;
import java.util.ServiceLoader;
import com.socket.spi.NetworkSocketProvider;
public abstract class NetworkSocket implements Closeable {
protected NetworkSocket() { }
public static NetworkSocket open() {
ServiceLoader<NetworkSocketProvider> sl
= ServiceLoader.load(NetworkSocketProvider.class);
Iterator<NetworkSocketProvider> iter = sl.iterator();
if (!iter.hasNext())
throw new RuntimeException("No service providers found!");
NetworkSocketProvider provider = iter.next();
return provider.openNetworkSocket();
}
}
src\\com.socket\\com\\socket\\spi\\NetworkSocketProvider.java
程式碼如下
package com.socket.spi;
import com.socket.NetworkSocket;
public abstract class NetworkSocketProvider {
protected NetworkSocketProvider() { }
public abstract NetworkSocket openNetworkSocket();
}
src\\org.fastsocket\\module-info.java
module org.fastsocket {
requires com.socket;
provides com.socket.spi.NetworkSocketProvider
with org.fastsocket.FastNetworkSocketProvider;
}
src\\org.fastsocket\\org\\fastsocket\\FastNetworkSocketProvider.java
程式碼如下
package org.fastsocket;
import com.socket.NetworkSocket;
import com.socket.spi.NetworkSocketProvider;
public class FastNetworkSocketProvider extends NetworkSocketProvider {
public FastNetworkSocketProvider() { }
@Override
public NetworkSocket openNetworkSocket() {
return new FastNetworkSocket();
}
}
src\\org.fastsocket\\org\\fastsocket\\FastNetworkSocket.java
程式碼如下
package org.fastsocket;
import com.socket.NetworkSocket;
class FastNetworkSocket extends NetworkSocket {
FastNetworkSocket() { }
public void close() { }
}
src/com.greetings/module-info.java
程式碼如下
module com.greetings {
requires com.socket;
}
src\\com.greetings/com\\greetings\\Main.java
程式碼如下
package com.greetings;
import com.socket.NetworkSocket;
public class Main {
public static void main(String[] args) {
NetworkSocket s = NetworkSocket.open();
System.out.println(s.getClass());
}
}
接下來是編譯,根據依賴順序
先編譯com.socket
javac -d mods --module-source-path src src\\com.socket\\module-info.java src\\com.socket\\com\\socket\\NetworkSocket.java src\\com.socket\\com\\socket\\spi\\NetworkSocketProvider.java
再編譯org.fastsocket
javac -d mods --module-source-path src src\\org.fastsocket\\module-info.java src\\org.fastsocket\\org\\fastsocket\\FastNetworkSocket.java src\\org.fastsocket\\org\\fastsocket\\FastNetworkSocketProvider.java
最後編譯com.greetings
javac -d mods --module-source-path src src\\com.greetings\\module-info.java src\\com.greetings\\com\\greetings\\Main.java
最後執行
java -p mods -m com.greetings/com.greetings.Main
控制台列印出結果
class org.fastsocket.FastNetworkSocket
以上輸出確認了服務提供者已經被定位,並且它被用作NetworkSocket
的工廠。
示例5
jlink
是鏈接器工具,可以用來鏈接一組模組,以及它們的橫向依賴關係,以創建一個自定義的模組化運行時鏡像(見JEP 220)。
該工具目前要求模組路徑上的模組以模組化的JAR
或JMOD
格式打包。JDK
構建以JMOD
格式打包標準模組和JDK
專用模組。
下面的例子創建了一個運行時鏡像,其中包含com.greetings模組和它的橫向依賴:
jlink --module-path $JAVA_HOME/jmods:mlib --add-modules com.greetings --output greetingsapp
--module-path
的值是一個包含打包模組的目錄PATH
。在Microsoft Windows
上,將路徑分隔符’:’替換為’;’。
$JAVA_HOME/jmods
是包含java.base.jmod
及其他標準和 JDK
模組的目錄。
模組路徑上的mlib
目錄包含com.greetings
模組的工件。
jlink
工具支援許多高級選項來訂製生成的影像,更多選項見jlink --help
示例6
–patch-module
從Doug Lea的CVS中籤出java.util.concurrent
類的開發者將習慣於用-Xbootclasspath/p
來編譯源文件和部署這些類。
-Xbootclasspath/p
已經被刪除,它的模組替代品是選項--patch-module
,用於覆蓋模組中的類。它也可以被用來增加模組的內容。javac
也支援--patch-module
選項,可以像模組的一部分一樣編譯程式碼。
下面是一個編譯新版本的java.util.concurrent.ConcurrentHashMap
並在運行時使用它的例子:
javac --patch-module java.base=src -d mypatches/java.base \
src/java.base/java/util/concurrent/ConcurrentHashMap.java
java --patch-module java.base=mypatches/java.base ...
程式碼
參考資料
Project Jigsaw: Module System Quick-Start Guide
Java 9 Modules – Developing Java 9 Modules with Apache Maven
Java 9 module example using maven