隨著 java 9 的正式發布,java生態又迎來了新的變革,模塊化是最重要的變化。
由于Jigsaw項目Java 9 跳票了很多次,你可能已經聽說過很多關于模塊、模塊化和其他的一些東西,而這些都是什么?模塊化是什么鬼?模塊化平臺對我們來說意味什么?什么是java平臺模塊化系統(JPMS)?它將在java生態中帶來什么革命性變化?
這篇文章是我對JDK中關于模塊化系統發生的重要變化的探索。我將解釋什么是模塊化,你為什么需要它,你怎么樣能創建自己的模塊化項目。
What & Why
可維護性是軟件設計和演進的過程中最重要的問題,我們想要一個松耦合、高內聚、高可讀性并且一眼就能理解的代碼庫。我們以package的形式設計和組織類文件,到目前為止還不錯,但是當我們有數百個package的時候,它們之間的依賴關系一眼是看不完的。因此我們需要比package更多的組織代碼庫的方法,來提高代碼庫的可維護性。
另外一個問題是java classpath,它是如何運行我們的代碼的,所有的jar類和類庫都是堆積在classpath中。當這些jar文件中的類在運行的時候有多個版本的時候,java的ClassLoader僅能加載那個類的一個版本。在這種情形下,你的程序的運行就會有歧義,歧義是一件非常壞的事情。這個問題是頻繁出現的,它被稱為是“JAR Hell”。
另外一個關于classpath的問題是,它不遵循“Fail First”原則。你可能會丟失一些類文件,這些文件存在于classpath中但是在生產環境不存在,這個問題只能在運行時報出JavaClassDefError異常的時候,你才能確定缺失了什么類文件。最后,classpath的最大問題在于“封裝”,在classpath中的每個類互相訪問,這是違反“封裝”特性的。我們想隱藏內部的API,這就是我們為什么需要另一個級別的封裝(更強的封裝),并且對package中的類的訪問做控制。
模塊就是修復這些問題的,什么是模塊?每個模塊都有一個名字,它將相關代碼組合在一起,是自包含的。模塊中明確地描述了它需要的其他模塊,以及自己對其他模塊可見的部分。在這種方式下,模塊之間的依賴是非常清晰的。我們有更強的封裝性,這意味著我們可以隱藏內部的API,并且現在我們也遵循“Fail First”原則,因此當有模塊缺失或者沖突的時候,你將得到一個錯誤。
模塊化的JDK允許JDK的開發者管理龐大復雜的自己。如果你在開發一個小而簡單的應用,并不使用RMI、CORBA、JavaEE和其他的東西,那你為什么需要一個完整的、巨大而又重的Java運行環境呢?只使用包含你需要的模塊的JDK,豈不是明智的。現在的模塊化平臺是這種情形成為可能。
這是現在的JDK看起來的樣子。在最底部是“java.base”模塊,每一個其它的模塊都顯式或隱式地依賴于它。正如你看到的這樣,依賴關系圖是一個有向無環圖,這意味著不允許出現循環依賴。
下圖基本上展示了模塊是什么,每一個模塊都有一個描述模塊的文件,叫做“module-info.java”。
在文件“module-info.java”中你描述了模塊的名字、運行需要的模塊和其中對外可見的包。例如,你可以看到java.sql模塊中對外暴露了哪些包和它需要哪些模塊。
因此簡化一下,module-info.java 的內容看起來像下面這樣子:
下面我們將介紹如何使用這些模塊和創建自己的模塊。
How
首先,你需要下載和安裝JDK 9,你可以在這里找到它。
Java Version
$ java -version
java version "9"
Java(TM) SE Runtime Environment (build 9+181)
Java HotSpot(TM) 64-Bit Server VM (build 9+1
下面我們在IntelliJ IDEA中構建一個項目:
下面介紹如何創建一個模塊:
創建模塊之后,你需要在模塊下的src下創建一個module-info.java文件:
我已經構建了一個項目包含兩個模塊:“com.rabbit”和“com.rabbit.log”,你可以看到項目的結構如下圖:
在模塊 com.rabbit.log 中有兩個類:InternalHello和Hello。
InternalHello.java
package hello.log.internals;
public class InternalHello {
public static String sayHello(String name){
return "Hello, This Greeting is internal dear "+ name;
}
}
Hello.java
package hello.log;
public class Hello {
public static String sayHello(String name){
return "Hello, " + name;
}
}
在模塊 com.rabbit.log 的modu-info.java中,寫入如下所示:
module com.rabbit.log {
exports hello.log;
}
這意味著package hello.log 在該模塊外可見的,而package hello.log.internals是不可見的。
RabbitMain是一個簡單的JavaFX程序:
package com.rabbit;
import hello.log.Hello;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class RabbitMain extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Label label = new Label(Hello.sayHello("Hossein"));
StackPane pane = new StackPane();
pane.getChildren().add(label);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
這個模塊看起來不需要暴露任何package,它需要 javafx.base 和 javafx.controls兩個模塊,為了使用Hello類還需要模塊com.rabbit.log。因此模塊 com.rabbit 的module-info.java看起來是下面這樣子:
module com.rabbit {
requires javafx.base;
requires javafx.controls;
requires com.rabbit.log;
}
當我們運行這個應用時,出現了如下的錯誤:
Caused by: java.lang.IllegalAccessException: class com.sun.javafx.application.LauncherImpl (in module javafx.graphics) cannot access class com.rabbit.RabbitMain (in module com.rabbit) because module com.rabbit does not export com.rabbit to module javafx.graphics
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:589)
這個錯誤很明顯地告訴我們,需要將package com.rabbit暴露出去。意思是 javafx.graphics 是需要使用RabbitMain啟動主窗口的,所以你需要暴露你的package給 javafx.graphics(主要:你可以暴露一個package給指定的模塊而不是暴露給所有的模塊)。
因此,現在的 module-info.java 是下面的樣子:
module com.rabbit {
requires javafx.base;
requires javafx.controls;
requires com.rabbit.log;
exports com.rabbit to javafx.graphics;
}
在java 9中實現的javaFX看起來好像有bug(注意黃色的圈),下面就是我們的javaFX程序的運行結果:
這個故事到這里并沒有結束,關于模塊以及它們之間的依賴關系的更加完整詳細的介紹可以閱讀書籍 Java 9 Revealed 或者 Java 9 Modularity 。
參考的原文鏈接。