模組基礎
module是用來組織packages,通過模組名稱(module’s name)來隔離不同模組之間包的可讀性。因此是在Java的訪問限制機制public/private/protected上加了一層,先有模組的可讀性才有訪問性可言。同時jlink工具提供了提取程式最小執行時的支援。
模組化機制給Java語言帶來一些新的關鍵字如下:
聰明的你可能會想起:在一個成熟的系統引入新的關鍵字,會不會對已有程式碼命名造成衝突。答案是:不會。因為這些關鍵字的使用限定在模組定義檔案module-info.java,你在正常的Java程式碼中還是可以使用這些關鍵字作為標示符。
下面先稍微簡單介紹下上面關鍵字的作用:
exports和requires是一對兒,前者用於匯出模組的某些包,後者則是匯入需要引入的外部模組的包。
provides/uses/with則是服務提供者機制下的用到的關鍵字,這個後文有講解。
transitive顧名思義就是指的可傳遞性,主要用於多個模組序列依賴用於簡化書寫requires語句的。
to主要是和exports組合,用於將模組的包匯出到特定模組,成為其專屬女傭。
open/opens主要是用於將模組開放給其他所有模組,但是要注意編譯期和執行期的區別。其具體機制後門也會詳述。
好了,模組的基本概念就到這裡了。聰明的你更期望的是一個能執行的例項吧,那我們下面就開始從程式碼的角度去使用我們的模組化機制咯。
簡單的模組示例
下面我們將定義三個模組,這裡還是沿用商品和訂單的栗子。我們定義的三個模組組織如下:
這裡我們定義三個模組:goods/order/pojo,其依賴關係如下:
goods->order->pojo
接下來我們依次建立好程式碼目錄和 Java檔案。當然你也可以手動建立這些目錄和程式碼。
pojo模組
我們先定義兩個實體類Goods.java和Order.java:
package pojo;
import java.util.List;
public class Goods {
private String goodsName;
private double price;
private List<Order> orderList;
public String getGoodsName() {
return goodsName;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public List<Order> getOrderList() {
return orderList;
}
public void setOrderList(List<Order> orderList) {
this.orderList = orderList;
}
@Override
public String toString() {
return "Goods{" +
"goodsName='" + goodsName + '\'' +
", orderList=" + orderList +
'}';
}
}
package pojo;
import java.time.LocalDateTime;
public class Order {
private LocalDateTime createTime;
private LocalDateTime finishTime;
private String orderName;
private String orderUser;
public Order(String orderName) {
this.orderName = orderName;
this.createTime = LocalDateTime.now();
this.finishTime = LocalDateTime.now().plusDays(2);
this.orderUser = "gxf";
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getFinishTime() {
return finishTime;
}
public void setFinishTime(LocalDateTime finishTime) {
this.finishTime = finishTime;
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
public String getOrderUser() {
return orderUser;
}
public void setOrderUser(String orderUser) {
this.orderUser = orderUser;
}
@Override
public String toString() {
return "Order{" +
"createTime=" + createTime +
", finishTime=" + finishTime +
", orderUser='" + orderUser + '\'' +
'}';
}
}複製程式碼
接下來在模組的根目錄建立module-info.java(注意模組宣告檔案必須在模組原始碼檔案的根目錄):
module pojo {
exports pojo;
}複製程式碼
至此我們定義了名為pojo的模組,並且匯出pojo這個包以供其他模組使用。
order模組
這裡我們定義一個OrderService類提供商品訂單查詢方法,其程式碼如下:
package order;
import pojo.Order;
import java.util.List;
public class OrderService {
public List<Order> queryOrdersByGoodsName(String goodsName) {
return List.of(new Order("oder1"),new Order("oder2")
,new Order("oder3"),new Order("oder4"),new Order("oder5"));
}
}
複製程式碼
queryOrdersByGoodsName方法構造訂單物件列表,注意這裡使用Java10集合新的工廠方法來生成的List。
order模組依賴pojo模組,因此其module-info.java定義如下:
module order{
exports order;
requires pojo;
}複製程式碼
我們看到該模組依賴pojo同時匯出order包,以為下面的goods模組依賴order。
goods模組
該模組定義GoodsService類,用於查詢商品資訊,其程式碼如下:
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) {
String module = "test",exports = "test",with="with";
System.out.println(new GoodsService().queryGoodsInfoByName("test"));
}
}
複製程式碼
接下來也要定義其模組宣告:
module goods{
requires order;
requires pojo;
}複製程式碼
因為暫時沒有其他模組依賴goods,所以模組定義裡沒有宣告其匯出的包。
至此我們的三個模組已經建立完成。
編譯和執行模組
編譯和執行您可以藉助IDE工具,但是為了更透徹的理解模組的定義,我們先採用javac命令來編譯和執行我們的模組。
按照模組的依賴順序,我們依次編譯pojo->order->goods模組。
編譯pojo模組
我們使用javac進行編譯,編譯命令如下:
javac -verbose --module-path target -d target\pojo pojo\src\*.java pojo\src\pojo\*.java
--module-path指定本次編譯依賴的外部模組的搜尋路徑,因為pojo模組沒有外部依賴,這個引數在此其實可以忽略。
-d指定編譯目標檔案存放的目錄
-verbose列印詳細日誌便於我們觀察編譯的具體過程
執行此命令控制檯列印資訊系如下:
D:\code\mods>javac -verbose --module-path target -d target\pojo pojo\src\*.java pojo\src\pojo\*.java
[語法分析開始時間 SimpleFileObject[D:\code\mods\pojo\src\module-info.java]] [語法分析已完成, 用時 23 毫秒] [語法分析開始時間 SimpleFileObject[D:\code\mods\pojo\src\pojo\Goods.java]] [語法分析已完成, 用時 9 毫秒] [語法分析開始時間 SimpleFileObject[D:\code\mods\pojo\src\pojo\Order.java]] [語法分析已完成, 用時 2 毫秒] [正在載入/modules/java.base/module-info.class] [正在載入/modules/java.base/java/util/List.class] [正在載入/modules/java.base/java/lang/Object.class] [正在載入/modules/java.base/java/lang/String.class] [正在載入/modules/java.base/java/time/LocalDateTime.class] [正在載入/modules/java.base/java/lang/Deprecated.class] [正在載入/modules/java.base/java/lang/Override.class] [正在載入/modules/java.base/java/lang/annotation/Annotation.class] [正在載入/modules/java.base/java/lang/annotation/Retention.class] [正在載入/modules/java.base/java/lang/annotation/RetentionPolicy.class] [正在載入/modules/java.base/java/lang/annotation/Target.class] [正在載入/modules/java.base/java/lang/annotation/ElementType.class] [正在檢查<匿名>] [已寫入DirectoryFileObject[target\pojo:module-info.class]] [正在檢查pojo.Goods] [正在載入/modules/java.base/java/io/Serializable.class] [正在載入/modules/java.base/java/lang/AutoCloseable.class] [正在載入/modules/java.base/java/lang/Byte.class] [正在載入/modules/java.base/java/lang/Character.class] [正在載入/modules/java.base/java/lang/Short.class] [正在載入/modules/java.base/java/lang/Long.class] [正在載入/modules/java.base/java/lang/Float.class] [正在載入/modules/java.base/java/lang/Integer.class] [正在載入/modules/java.base/java/lang/Double.class] [正在載入/modules/java.base/java/lang/Boolean.class] [正在載入/modules/java.base/java/lang/Void.class] [正在載入/modules/java.base/java/lang/invoke/StringConcatFactory.class] [正在載入/modules/java.base/java/lang/invoke/MethodHandles.class] [正在載入/modules/java.base/java/lang/invoke/MethodHandles$Lookup.class] [正在載入/modules/java.base/java/lang/invoke/MethodType.class] [正在載入/modules/java.base/java/lang/invoke/CallSite.class] [已寫入DirectoryFileObject[target\pojo:pojo/Goods.class]] [正在檢查pojo.Order] [正在載入/modules/java.base/java/time/temporal/Temporal.class] [正在載入/modules/java.base/java/time/temporal/TemporalAccessor.class] [正在載入/modules/java.base/java/time/temporal/TemporalAdjuster.class] [正在載入/modules/java.base/java/time/chrono/ChronoLocalDateTime.class] [正在載入/modules/java.base/java/lang/Comparable.class] [已寫入DirectoryFileObject[target\pojo:pojo/Order.class]] [共 485 毫秒]
上面的輸出資訊表明:雖然我們的pojo沒有顯示宣告requires哪些模組,但是系統預設匯入了java.base模組。也就是說我們定義的模組都隱士依賴java.base模組,這僅僅是為了匯入常見的一些Java類庫。其他輸出資訊讀者可以自行檢視。
編譯order模組
類似的執行下邊命令即可:
D:\code\mods>javac -verbose --module-path target -d target\order order\src\*.java order\src\order\*.java
[語法分析開始時間 SimpleFileObject[D:\code\mods\order\src\module-info.java]] [語法分析已完成, 用時 34 毫秒] [語法分析開始時間 SimpleFileObject[D:\code\mods\order\src\order\OrderService.java]] [語法分析已完成, 用時 2 毫秒] [正在載入target\pojo\module-info.class] [正在載入/modules/java.base/module-info.class] [正在載入target\pojo\pojo\Order.class] [正在載入/modules/java.base/java/util/List.class] [正在載入/modules/java.base/java/lang/Object.class] [正在載入/modules/java.base/java/lang/String.class] [正在載入/modules/java.base/java/lang/Deprecated.class] [正在載入/modules/java.base/java/lang/annotation/Retention.class] [正在載入/modules/java.base/java/lang/annotation/RetentionPolicy.class] [正在載入/modules/java.base/java/lang/annotation/Target.class] [正在載入/modules/java.base/java/lang/annotation/ElementType.class] [正在檢查<匿名>] [已寫入DirectoryFileObject[target\order:module-info.class]] [正在檢查order.OrderService] [正在載入/modules/java.base/java/io/Serializable.class] [正在載入/modules/java.base/java/lang/AutoCloseable.class] [正在載入/modules/java.base/java/util/Collection.class] [正在載入/modules/java.base/java/lang/Iterable.class] [已寫入DirectoryFileObject[target\order:order/OrderService.class]] [共 684 毫秒]
輸出資訊我們可以清楚的看到,該模組先載入target\pojo\module-info.class模組然後載入java.base模組。
編譯goods模組
D:\code\mods>javac -verbose --module-path target -d target\goods goods\src\*.java goods\src\goods\*.java
[語法分析開始時間 SimpleFileObject[D:\code\mods\goods\src\module-info.java]] [語法分析已完成, 用時 32 毫秒] [語法分析開始時間 SimpleFileObject[D:\code\mods\goods\src\goods\GoodsService.java]] [語法分析已完成, 用時 7 毫秒] [正在載入target\order\module-info.class] [正在載入target\pojo\module-info.class] [正在載入/modules/java.base/module-info.class] [正在載入target\order\order\OrderService.class] [正在載入target\pojo\pojo\Goods.class] [正在載入/modules/java.base/java/lang/Object.class] [正在載入/modules/java.base/java/lang/String.class] [正在載入/modules/java.base/java/lang/Deprecated.class] [正在載入/modules/java.base/java/lang/annotation/Retention.class] [正在載入/modules/java.base/java/lang/annotation/RetentionPolicy.class] [正在載入/modules/java.base/java/lang/annotation/Target.class] [正在載入/modules/java.base/java/lang/annotation/ElementType.class] [正在檢查<匿名>] [已寫入DirectoryFileObject[target\goods:module-info.class]] [正在檢查goods.GoodsService] [正在載入/modules/java.base/java/io/Serializable.class] [正在載入/modules/java.base/java/lang/AutoCloseable.class] [正在載入/modules/java.base/java/util/List.class] [正在載入target\pojo\pojo\Order.class] [正在載入/modules/java.base/java/lang/System.class] [正在載入/modules/java.base/java/io/PrintStream.class] [正在載入/modules/java.base/java/lang/Appendable.class] [正在載入/modules/java.base/java/io/Closeable.class] [正在載入/modules/java.base/java/io/FilterOutputStream.class] [正在載入/modules/java.base/java/io/OutputStream.class] [正在載入/modules/java.base/java/io/Flushable.class] [已寫入DirectoryFileObject[target\goods:goods/GoodsService.class]] [共 650 毫秒]
至此我們完成了所有模組的編譯,現在我們來看看編譯結果:
執行
我們的執行入口在goods.GoodsService,執行命令如下:
D:\code\mods>java --module-path target -m goods/goods.GoodsService
Goods{goodsName='test', orderList=[Order{createTime=2018-06-25T14:56:53.541093100, finishTime=2018-06-27T14:56:53.541093100, orderUser='gxf'}, Order{createTime= 2018-06-25T14:56:53.541093100, finishTime=2018-06-27T14:56:53.541093100, orderUser='gxf'}, Order{createTime=2018-06-25T14:56:53.541093100, finishTime=2018-06-27 T14:56:53.542053500, orderUser='gxf'}, Order{createTime=2018-06-25T14:56:53.542053500, finishTime=2018-06-27T14:56:53.542053500, orderUser='gxf'}, Order{createT ime=2018-06-25T14:56:53.542053500, finishTime=2018-06-27T14:56:53.542053500, orderUser='gxf'}]}
--module-path指定模組根目錄,也就是我們編譯結果存放的根目錄
-m指定程式的入口,其用法是:
-m <module>/<main-class>
因此必須是帶模組名然後是主類名。
好了,至此模組的基礎知識我們已經掌握的差不多了。如果你不喜歡命令列,則可以考慮使用idea來進行編譯。在idea編譯的時候會報錯找不到模組,根據提示將依賴模組匯入即可。
最後關於程式碼下載
因為程式碼量很少,建議大家還是手動建立一遍,畢竟程式碼都可以複製貼上了。自己實踐一遍是不是印象更深刻呢。
由於篇幅和時間關係,關於模組的其他知識我會另起一篇再進行講述,希望大家每天都有進步。