模塊化是 Java 9 一個(gè)非常重要的特性,終于有時(shí)間整理一下這方面的內(nèi)容了。
模塊化是軟件工程中非常重要的一個(gè)概念。把獨(dú)立的功能封裝成模塊,并提供接口供外部使用是我們?cè)陂_發(fā)中努力實(shí)現(xiàn)的目標(biāo)。模塊化有很多好處:
- 代碼內(nèi)聚,容易維護(hù);
- 能夠有效降低復(fù)雜度;
- 能提供更好的伸縮性和擴(kuò)展性。
Java 9 的 Jigsaw 項(xiàng)目致力于為 Java 9 帶來平臺(tái)級(jí)的模塊化系統(tǒng)。這個(gè)項(xiàng)目從 2008 年就已經(jīng)開始,由于很多原因一直沒有發(fā)布?,F(xiàn)在,Jigsaw 涵蓋了 6 個(gè) JEP,終于作為 Java 9 的一部分發(fā)布出來。
從整體到模塊
Java 一直堅(jiān)持向后兼容,每更新一個(gè)版本,都變得更大更復(fù)雜。在過去的 JDK 版本中,沒有強(qiáng)制要求模塊化,使得 Java 平臺(tái)和 JDK 越來越讓人糾結(jié)。即便只是一個(gè)小小的 hello world,也需要加載幾乎所有的 API。Jigsaw 項(xiàng)目致力于把 Java 平臺(tái)和 JDK 分解成更小更有組織的模塊。用戶可以使用模塊來構(gòu)建軟件,并且不需要包含所有的 API。
這項(xiàng)工作并不容易,Oracle 的 Java 平臺(tái)組首席架構(gòu)師 Mark Reinhold 曾經(jīng)說過:
JDK代碼庫在API和實(shí)現(xiàn)級(jí)別上都是相互關(guān)聯(lián)的,多年來主要以單片軟件系統(tǒng)的方式構(gòu)建。我們花了相當(dāng)多的努力消除或至少簡化了盡可能多的API和實(shí)現(xiàn)依賴關(guān)系,以便平臺(tái)和它的實(shí)現(xiàn)可以作為一組相互依賴的模塊來呈現(xiàn),但是一些特別棘手的情況仍然存在。我們希望盡可能多地保持與先前版本的兼容性,特別是對(duì)于現(xiàn)有的基于類路徑的應(yīng)用程序,同時(shí)在可能的情況下,對(duì)于由模塊組成的應(yīng)用程序也是如此。
所有 Java 9 模塊都在 $JAVA_HOME/jmods 目錄下面。每個(gè)模塊都具有暴露給其他模塊的接口。這些模塊互相依賴,并通過導(dǎo)出的包進(jìn)行交互。 開發(fā)人員可以編譯、打包、部署和執(zhí)行僅由所選模塊組成的應(yīng)用程序,而無需其他任何操作。
如何使用模塊
下面我們把自己的 Java 代碼也進(jìn)行模塊化。新建兩個(gè)簡單的模塊:
- com.timeteller.clock:包含SpeakingClock的模塊,用于將當(dāng)前時(shí)間打印到stdout的簡單類;
- com.timeteller.main:使用com.timeteller.clock模塊提供的功能的模塊。
整個(gè)項(xiàng)目結(jié)構(gòu)如下所示:
├── com.timeteller.clock
│ ├── com
│ │ └── timeteller
│ │ └── clock
│ │ └── SpeakingClock.java
│ └── module-info.java
└── com.timeteller.main
├── com
│ └── timeteller
│ └── main
│ └── Main.java
└── module-info.java
在 main 函數(shù)中調(diào)用方法與之前并無多少不同。
public static void main (String[] args) {
SpeakingClock clock = new SpeakingClock();
clock.tellTheTime(); // displays the time to stdout.
}
Java 9 平臺(tái)級(jí)模塊化系統(tǒng)最重要的部分在 module-info.java 中,這個(gè)文件定義了模塊和元數(shù)據(jù)。內(nèi)容如下所示。一個(gè)文件定義了 com.timeteller.clock 模塊,并導(dǎo)出了 com.timeteller.clock 包;另一個(gè)文件定義了 com.timeteller.main 模塊,并引用了 com.timeteller.clock 模塊。
module com.timeteller.clock {
exports com.timeteller.clock;
}
module com.timeteller.main {
requires com.timeteller.clock;
}
module-info.java 的配置比較簡單,可以看到:
- 模塊描述符文件放置在模塊的根文件夾中;
- 每個(gè)模塊都有一個(gè)唯一的名稱;
- 模塊描述符定義從模塊導(dǎo)出的包以及它們需要哪些模塊;
模塊中的包如果沒有導(dǎo)出,它的作用域就僅限于當(dāng)前模塊中,其他模塊無法使用。這一特性使得 Java 9 中的 public 含義有所變化,模塊中聲明為 public 的類不再是可以隨意訪問的,只有導(dǎo)出以后才能從模塊外訪問到?;谶@個(gè)特性,可以有效地隱藏模塊內(nèi)的 API。
編譯 Java 程序
編譯時(shí)可以這樣打包:
$ mkdir -p jars
$ mkdir -p build/classes/com.timeteller.clock
$ javac -d build/classes/com.timeteller.clock/ com.timeteller.clock/module-info.java com.timeteller.clock/com/timeteller/clock/SpeakingClock.java
$ jar -c -f jars/clock.jar -C build/classes/com.timeteller.clock/ .
$ mkdir -p build/classes/com.timeteller.main
$ javac -d build/classes/com.timeteller.main/ --module-path=jars com.timeteller.main/module-info.java com.timeteller.main/com/timeteller/main/Main.java
$ jar -c -f jars/timeTeller.jar --main-class com.timeteller.main.Main -C build/classes/com.timeteller.main .
我們必須告訴編譯器模塊的路徑(modulepath),編譯器才能準(zhǔn)確編譯。模塊路徑的概念與 classpath 的概念非常相似。當(dāng)我們構(gòu)建 com.timeteller.main 模塊時(shí),需要在它的模塊路徑上放一個(gè)帶有 com.timeteller.clock 的 jar。否則編譯器找不到必需的代碼并會(huì)導(dǎo)致編譯錯(cuò)誤:
$ javac -d build/classes/com.timeteller.main/ com.timeteller.main/module-info.java com.timeteller.main/com/timeteller/main/Main.java
com.timeteller.main/module-info.java:2: error: module not found: com.timeteller.clock
requires com.timeteller.clock;
這里編譯后創(chuàng)建的 jar 也是模塊化,模塊的 jar 與常規(guī)的 jar 基本一樣,除了里面包括 module-info.class 文件。Java 9 即將到來,讓我們一起期待吧。
分享學(xué)習(xí)筆記和技術(shù)總結(jié),內(nèi)容涉及 Java 進(jìn)階、架構(gòu)設(shè)計(jì)、前沿技術(shù)、算法與數(shù)據(jù)結(jié)構(gòu)、數(shù)據(jù)庫、中間件等多個(gè)領(lǐng)域。關(guān)注作者第一時(shí)間獲取最新內(nèi)容,公眾號(hào)同名(閱讀體驗(yàn)更佳)。