在上篇基礎部分介紹了模組化系統的基本使用和使用命令編譯和執行我們的模組。
再來看下模組相關的關鍵字:
目前為止我們熟悉的就是exports/reuires/module,其使用也是很簡單的。下面將逐步介紹其他關鍵字的使用場景。
系統模組
Java9以後JDK本身也是模組化的,如果我們使用模組化機制那麼引用系統的包和物件,也需要在模組定義中引入這些包的依賴模組。
其中java.base較為特殊。這個模組由編譯器自動新增到我們的自定義模組依賴宣告中。我們看看JDK原始碼中是如何定義該模組的:
C:/Program Files/Java/jdk-10.0.1/lib/src.zip!/java.base/module-info.java
該模組匯出了java.io/java.nio/java.lang/java.net/java.math/java.time/java.util等核心基礎包,這樣我們的程式碼中就不需要顯示去匯入這些包所在模組的依賴宣告瞭。
遺留程式碼和匿名模組
由於Java9模組機制可以說是breaking change,所以他需要解決舊程式碼的相容。如果我們的程式碼中不宣告module-info.java,那麼我們的程式碼在一個稱為“unnamed module”模組中。
“unnamed module”有兩個要的特性。首先,該模組裡的所有包預設都會自動進行exports宣告,因此其他外部模組對他的所有物件可讀。其次,匿名模組能訪問所有其他模組(當然是有許可權的情況下)。
當我們程式碼不顯示宣告模組定義的時候,編譯程式碼仍然使用的classpath機制而不是上文提到的mudule path機制。正是這兩個預設機制才能保證我們的專案從Java8及以下可以直接切換到Java10。而不需要對新的模組機制進行適配。
所以JDK的開發者在這方面還是付出了很多努力的,在此對他們表示深感欽佩。
匯出到特定模組
如果你的模組中的某些物件只允許特定的模組使用,則可以使用to關鍵字:
exports packageName to moduleName1,moduleName1,...;複製程式碼
to後面可以新增多個模組,修改pojo模組定義:
module pojo {
exports pojo to order;
}複製程式碼
我們再編譯goods模組就會報錯:
D:\code\mods>javac --module-path target -d target\goods goods\src\*.java goods\src\goods\*.java
goods\src\goods\GoodsService.java:5: 錯誤: 程式包 pojo 不可見 import pojo.Goods; ^ (程式包 pojo 已在模組 pojo 中宣告, 但該模組未將它匯出到模組 goods) 1 個錯誤
可傳遞性transitive
上文的三個模組就是可傳遞性的栗子:goods->order->pojo,同時goods也依賴pojo。來看看目前goods模組的定義:
module goods{
requires order;
requires pojo;
}複製程式碼
顯然requires pojo;和order模組中重複了,我們只需修改order的模組宣告:
module order {
exports order;
requires transitive pojo;
}
複製程式碼
module goods{
requires order;
}複製程式碼
模組的開放
open module
一個模組裡的包只有在模組中宣告exports,別的模組才能進行訪問。當然,我們要明白這裡的訪問指的的是靜態編譯期間的訪問。其定義如下:
open module moduleName{
//模組定義
}複製程式碼
下面舉個栗子,我們在pojo模組新增一個包和類other.Other。這是一個空類啥都沒有:
package other;
public class Other {
}然後複製程式碼
修改pojo模組定義,加上open關鍵字:
open module pojo {
exports pojo;
}複製程式碼
我們在goods模組中使用:
GoodsService.java
package goods;
import order.OrderService;
import pojo.Goods;
public class GoodsService {
private OrderService orderService = new OrderService();
Goods queryGoodsInfoByName(String goodsName) {
Goods goods = new Goods();
goods.setGoodsName(goodsName);
goods.setOrderList(orderService.queryOrdersByGoodsName(goodsName));
return goods;
}
public static void main(String[] args) throws Exception{
String module = "test",exports = "test",with="with";
System.out.println(new GoodsService().queryGoodsInfoByName("test"));
//test open module
Object other = Class.forName("other.Other");
System.out.println(other);
}
}複製程式碼
上邊的栗子使用反射來建立Other的例項,執行程式發現確實可以。然而我們並沒有在pojo模組定義:
exports other;
可見open關鍵字使得pojo的包和物件在執行期開放給了其他模組,然而在編譯還是不開放的。修改示例程式碼:
Other other = new Other();複製程式碼
編譯是不能通過的。
opens Statement
我們想更細粒度的開放包的訪問性,可以使用opens把特定的包開放給外部模組,提供執行時訪問許可權。其使用如下:
opens packageName [to moduleName];複製程式碼
注意兩點:1、opens宣告不能用在open的module中,因為open module定義的模組所有包都有了open屬性。2、opens只是開放了執行時訪問許可權,而不是編譯期。3、opens可以指定具體的模組,只有指定的模組才能在執行時訪問該包。
我們可以看到系統模組也是用這個機制,比如java.desktp:
opens com.sun.java.swing.plaf.windows to jdk.jconsole;
opens javax.swing.plaf.basic to jdk.jconsole;複製程式碼
requires static
使用static那麼匯入的模組只是在編譯可用,在執行期是不可見的。使用該關鍵字則必須明確,引入的模組在執行期是可選的,否則用到就會報錯。比如我們改變goods模組的定義:
module goods{
requires static order;
}複製程式碼
然後編譯,一切OK。但是在執行的時候就會報錯:
Exception in thread "main" java.lang.NoClassDefFoundError: order/OrderService
at goods/goods.GoodsService.<init>(GoodsService.java:9)
at goods/goods.GoodsService.main(GoodsService.java:20)
Caused by: java.lang.ClassNotFoundException: order.OrderService
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:190)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:499)
... 2 more
Process finished with exit code 1
複製程式碼
總結
模組的基本使用到此已經差不多了,我們學會了:
- 如何定義模組
- 系統模組和匿名模組
- 模組依賴的可傳遞性
- 如何將模組匯出到目標模組
- 執行時開放模組
- 執行時開放指定模組下的包
- 靜態模組依賴