天天看點

Java 子產品化系統初探

java 子產品化系統自提出以來經曆了很長的時間,直到 2014 年晚些時候才最終以 jsr(jsr-376) 定稿,而且這個部分有可能在 java 9 中出現。但是一直以來都沒有可以使用的原型。9 月 11 日,openjdk 釋出的早期建構版本終于包含了 jigsaw 項目。

Java 子產品化系統初探

昨天,我和同僚 paul bakker 在 javazone 上對于 java 子產品化系統進行了讨論。整個讨論都建立在jsr-376

需求文檔以及身邊一些珍貴的資訊上。在年初提出舉行這個報告的時候,我們曾深信不疑地認為在這個會上我們能夠展示一個原型,但是事情卻沒有按預想的那樣發

展。現在的情況是,這個原型将在我們的報告結束之後釋出。這也意味着,報告中的一些内容已經有點過時了,但是主要的思想還是很有新意的。如果你對

java 子產品化系統方案一無所知的話,建議你在閱讀這篇文章之前先去看一下我們的報告。我們的報告介紹了現在的方案,并進一步與 osgi 進行了比較。

為什麼要使用子產品?

什麼是子產品?我們為什又需要它們?如果希望有一個深入的讨論,請閱讀“state of the module system”或者看一下我們的報告。對這塊還不是很了解的人來說,這裡有cliff 的注釋版本。

我們都知道 java 有 jar 檔案。但是,事實上這些都隻是包含一些class(類)的壓縮檔案,這些 jar 包内部都是一些

package (包)。當你利用一些不同的 jar

包來運作應用程式的時候(複雜一點的程式也适用),你需要把它們放到指定的類路徑中。然後默默祈禱。因為沒有有效的工具來幫助你知道,你是否已經把應用所

需要的 jar 包都放入類路徑中了。或者有可能你在不經意間将同樣的類檔案(在不同的 jar 包中)都放入了類路徑中。類路徑災難(類似于 dll

災難)是真實存在的。這會導緻運作時出現糟糕的狀況。同時,在運作時我們也無法得知 jar 中包含哪些類。從 jre

角度來說隻知道有一堆類檔案。事實上 jar 包之間是互相依賴的,但目前還不能把這種依賴關系記錄到資料檔案中去。理想的情況是,你可以隐藏 jar

包中類檔案具體的實作,隻是提供一些公共的 api 。在 java 中提出子產品化系統就是為了解決這些問題的:

子產品成為首先要考慮的部分,它能夠分裝實作細節并且隻暴露需要的接口。

子產品準确地描述了他們能夠提供的接口,以及他們的需要部分(依賴)。由此,我們可以在開發的過程中弄清和處理依賴關系。

子產品系統極大地提升了大型系統的可維護性、可靠性、安全性。至少 jdk 本身還缺少這樣的系統。通過這樣的子產品系統,子產品圖能夠自動地建構。這個圖隻包括了你的應用程式運作時所須要的子產品。

安裝 jdk9 預覽版

如果你想親自嘗試編寫示例代碼,你需要安裝包含 jigsaw 原型的 jdk9 早期建構版本。在 osx

上,你需要解壓文檔,然後把解壓出來的目錄移動到 library/java/javavirtualmachines/

下。然後你需要設定環境變量,将 java_home 環境變量指向 jdk9 的目錄。我使用了非常好用的setjdk

腳本,通過它可以在指令視窗中實作 java 安裝的指令切換。你很有可能不願意使用這個早期建構版本作為你的 java 安裝版本。你可以通過

java -version 來确認安裝完成。輸出如下面所示:

1

2

3

<code>java version</code><code>"1.9.0-ea"</code>

<code>java(tm) se runtime environment (build 1.9.0-ea-jigsaw-nightly-h3337-20150908-b80)</code>

<code>java hotspot(tm) 64-bit server vm (build 1.9.0-ea-jigsaw-nightly-h3337-20150908-b80, mixed mode)</code>

一個簡單的例子

你仍舊可以通過類、jar包以及類路徑這樣“傳統方式”的方式來使用 jdk9 。但是明顯地我們想要采用子產品的方式。是以我們将建立一個包含兩個子產品的工程:子產品一使用了子產品二中的代碼。

首先要做的就是,建構我們的工程并把兩個子產品很好地區分開來。然後,子產品中需要以 module-info.java 檔案的形式添加中繼資料。我們的示例建構如下:

4

5

6

7

<code>src</code>

<code>  </code><code>module1</code>

<code>     </code><code>module-info.java</code>

<code>     </code><code>comtesttestclassmodule1.java</code>

<code>  </code><code>module2</code>

<code>     </code><code>commoretesttestclassmodule2.java</code>

接着,我們将介紹 package (包)層最頂上的一層(module1、 module2),這部分你在之前已經建構好了。在這些“子產品目錄”中,可以看到 module-info.java 檔案在根目錄下。此外請注意,這兩個類都是在顯示命名的包中的。

請看 testclassmodule1 的代碼:

8

9

10

11

12

13

<code>package</code> <code>com.test;</code>

<code>import</code> <code>com.moretest.testclassmodule2;</code>

<code>public</code> <code>class</code> <code>testclassmodule1 {</code>

<code>   </code><code>public</code> <code>static</code> <code>void</code> <code>main(string[] args) {</code>

<code>     </code><code>system.out.println(</code><code>"hi from "</code> <code>+ testclassmodule2.msg());</code>

<code>   </code><code>}</code>

<code>}</code>

看起來很普通對吧?這裡并沒有涉及子產品,而是導入了 testclassmodule2 ,主函數之後會去調用其中的 msg() 方法。

<code>package</code> <code>com.moretest;</code>

<code>public</code> <code>class</code> <code>testclassmodule2 {</code>

<code>   </code><code>public</code> <code>static</code> <code>string msg() {</code>

<code>     </code><code>return</code> <code>"from module 2!"</code><code>;</code>

到目前為止,module-info.java 還是空的。

對 java 子產品進行編譯

現在進行下一步:編譯我們的子產品,并關聯源檔案。為了做這項工作,我們将介紹一個新的 javac 編譯參數:

<code>javac -modulesourcepath src -d mods $(find src -name</code><code>'*.java'</code><code>)</code>

使用上面語句時,我們假設指令程式已經處于 src 檔案夾的上級目錄中了。-modulesourcepath 參數會讓 javac

從傳統編譯模式進入子產品模式。-d 标記指出了編譯好的子產品的輸出目錄。javac 将以非打封包件的形式輸出這些子產品。如果我們這之後想以 jars

的形式使用這些子產品的話,需要一個單獨的步驟。

那麼當我們調用上面的 javac 指令行的時候會發生什麼那?編譯出錯了!

<code>src</code><code>/module1/module-info</code><code>.java:1: error: expected</code><code>'module'</code>

<code>src</code><code>/module2/module-info</code><code>.java:1: error: expected</code><code>'module'</code>

空的 module-info.java

檔案導緻了這個錯誤。是以,一些新的關鍵字将被引入到這些檔案中來,這些都是子產品中非常重要的部分。這些關鍵字的作用域就是

module-info.java 的定義部分。你還可以在 java 的源檔案中使用 module 類型的變量。

我們采用了最少的描述資訊,并更新了子產品描述檔案:

<code>module module1 { }</code>

然後是子產品2:

<code>module module2{ }</code>

現在,子產品已經被準确地命名了,但是還沒有包含其它的資料。再次編譯會導緻新的錯誤:

<code>src</code><code>/module1/com/test/testclassmodule1</code><code>.java:3: error: testclassmodule2 is not visible because package com.moretest is not visible</code>

封裝出現了!預設情況下,子產品内部的類或者其他類型對外都是隐藏的。這就是 javac 不允許使用 testclassmodule2

的原因,即使它是一個公共的類。如果我們還是使用基于傳統類路徑的編譯的話,一切都可以正常運作。當然我們也可以通過明确地将

testclassmodule2 暴露給外部來解決這個問題。接下來的這些改變對于 module2 中的 module-info.java

來說是必須的:

<code>module module2 {</code>

<code>  </code><code>exports com.moretest;</code>

這還不夠。如果你将修改後的編譯,你會得到同樣的錯誤。那是因為,雖然現在 module2 已經暴露了所需的包(包含所有的公共類型),但是

module1 還沒有聲明它對 module2 的依賴。我們同樣可以改變 module1 的 module-info.java

檔案來解決這個問題:

<code>module module1 {</code>

<code>   </code><code>requires module2;</code>

通過指定名字的方法可以表示對其它子產品的依賴,盡管在這些子產品中是以包的形式導出的。這方面還有很多可以說的東西,但是我并不想在初步的介紹中涉

及。在做完這一步之後,我們使用 jigsaw 第一次成功編譯了多子產品項目。如果你打開 /mods

目錄,你能看到編譯出來的東西被整齊地劃分為兩個目錄。這就成功了!

運作子產品化代碼

隻是編譯的話并沒有多大樂趣。我們希望應用程式能夠運作起來。幸運的是,jre 和 jdk 已經在這個原型中支援子產品關聯。這個應用可以通過指定子產品路徑的方式來啟動,而不是類路徑:

<code>java -mp mods -m module1</code><code>/com</code><code>.</code><code>test</code><code>.testclassmodule1</code>

我們把子產品路徑指向 mods 檔案夾,這個檔案就是 javac 編譯時寫輸出子產品的地方。而 -m 指出了最初要啟動的子產品,通過這個子產品可以逐漸啟動其他子產品。我們同樣添加了在初始化時需要調用的啟動類的名字,運作結果如下所示:

<code>hi from from module 2!</code>

未來

這部分介紹可以讓你初步了解可以使用 java 9

中的子產品可以做什麼。這部分還是需要更多的探索。就像打包一樣:除了jar包,即将會有一種新的形式叫做 jmod

。這個子產品化系統同樣包括一個服務層,它可以通過接口綁定服務提供者和服務使用者。可以把這個看成反轉控制:子產品系統擔任服務注冊管理的角色。還有一個值

得期待的地方是,jdk 本身将會如何使用子產品化系統進行子產品化。這有可能支援一些非常棒的技術,比如建立一個運作時鏡像,這個鏡像可以隻包括 jdk

和你應用所需要的那些子產品。好處有:占用更少的空間,對于程式整體的優化可以有更多的選擇等等。這些前景都是很光明的。

我接下來将嘗試移植一個簡單的 osgi 應用程式(該程式會使用一些子產品和服務)到 java 9 子產品系統上。敬請關注!

作者:闵大為

來源:51cto