1. 什么是模塊化
本質上,模塊化就是將系統(tǒng)分解成獨立且相互連接的模塊的行為。java9模塊除了包含代碼外,一個重要特征是增加了描述模塊的文件:module-info.java
Q:為什么這么做?-- 減少復雜性
Jigsaw 項目從2008就開始對JDk模塊化進行探索,2014年開始進行設計和實現(xiàn)。
2. 模塊化JDK
JDK由90個左右的平臺模塊組成,每個模塊都是一個定義好的功能塊,下圖顯示了部分模塊的子集以及依賴關系:

頂部的兩個重要模塊:java.se.ee,java.se,主要用于對其他模塊進行邏輯分組,屬于聚合器模塊。
每個模塊都隱式依賴于java.base,因為該模塊公開了java.lang, java.util之類的包。
-
模塊之間依賴關系沒有循環(huán),java模塊系統(tǒng)不允許編譯時存在模塊循環(huán)依賴。
列出所有JDK模塊: java --list-modules查看模塊定義:java --describe-module java.rmi
3.模塊如何定義
模塊描述文件module-info.java:
module com.test{// 模塊名稱
requires java.sql; //依賴java.sql模塊,普通依賴
requires transitive java.xml; //依賴java.xml,可傳遞依賴
exports com.my.test1; //導出com.my.test1包到其他模塊
exports com.my.test2 to xx.xx.xx; //限制導出,導出com.my.test2到指定模塊
}
- 可讀性:模塊A依賴模塊B,意味著對模塊B可讀,也就是可以訪問模塊B導出包中的類型。普通依賴可讀性是不可傳遞的。
- 可訪問性:public、protected、private等訪問修飾符的可訪問性規(guī)則依舊適用。
4.模塊Demo
單個模塊
創(chuàng)建模塊
編譯
mkdir -p mods/helloworld
javac -d mods/helloworld helloworld/src/helloworld/module-info.java helloworld/src/helloworld/com/module/helloworld/HelloWorld.java
打包
mkdir mlib
方式1:jar -cfe mlib/helloworld.jar com.module.helloworld.HelloWorld -C mods/helloworld .
方式2:jar --create --file=mlib/com.greetings.jar --main-class=com.module.helloworld.HelloWorld -C mods/helloworld .
運行
1.以編譯后的文件運行模塊:
java --module-path mods --module helloworld/com.module.helloworld.HelloWorld
2.以模塊化jar包運行模塊:
java -p mlib -m helloworld
多個模塊
模塊間的引用
用Maven如何配置多個模塊
1.為每個模塊添加maven依賴,maven只是配置模塊路徑。
2.pom文件中配置編譯器插件為java9或以上
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>10</source>
<target>10</target>
</configuration>
</plugin>
</plugins>
</build>
3.給每個模塊添加module-info.java,并將依賴項作為requires語句添加。
模塊定義限制
模塊解析是一個遞歸的過程,從根模塊開始解析->requires/requires transitive所需的模塊->循環(huán)直到沒有其他任何依賴項。
模塊名稱必須唯一
Java模塊系統(tǒng)不支持編譯期循環(huán)依賴
只有一個模塊可以導出一個給定的包
模塊不能定義版本,版本信息必須存儲在模塊之外。如Maven通過pom文件選擇正確的模塊版本并設置到模塊路徑上
5. 服務
Java模塊系統(tǒng)的服務機制可以共享公共接口,并將實現(xiàn)代碼強封裝到未導出的包中,實現(xiàn)服務提供者和消費者的真正解耦。
module api {
exports com.calculate; //api模塊導出服務接口
}
//Caltulator接口代碼
public interface Calculator {
String getName();
BigDecimal calculate(BigDecimal v1, BigDecimal v2);
}
module provider1 {
requires api;
provides com.calculate.Calculator with com.provider.Sum;//該模塊提供了Calculator接口的一個實現(xiàn),并且不導出實現(xiàn)類
}
//Sum實現(xiàn)類代碼
public class Sum implements Calculator {
......
}
module com.consumer{
requires api;
uses com.calculate.Calculator; //該模塊想要消費Calculator的實現(xiàn)
}
消費端代碼:
public static void main(String[] args) {
ServiceLoader<Calculator> services = ServiceLoader.load(Calculator.class);
for (Calculator cal : services) {
cal.getName();
}
}
uses子句不要求Calculator實現(xiàn)在編譯期間可用,消費者和提供者在運行時才被綁定在一起,由此實現(xiàn)了消費者和提供者的完全解耦,提供了不用重新編譯、打包的可擴展性
-
消費者使用ServiceLoader獲取提供者實現(xiàn)對代碼是有一定侵入性的,有兩種改進的方式:
-
在api模塊接口中添加一個靜態(tài)工廠方法,該方法返回所有提供者實現(xiàn)
module api1 { exports com.calculate; //api模塊導出服務接口 uses com.calculate.Calculator; //該模塊想要消費Calculator的實現(xiàn) } //api模塊接口代碼 public interface Calculator { String getName(); BigDecimal calculate(BigDecimal v1, BigDecimal v2); static Iterable<Calculator> getCalculator(){ return ServiceLoader.load(Calculator.class); } }module com.consumer1{ requires api; } 消費端代碼: public static void main(String[] args) { Iterable<Calculator> services = Calculator.getCalculator(); } -
使用開放模塊和開放包,消費者通過@Inject依賴注入服務提供者類。
open module api{// 開放模塊中的所有包,任何模塊都可以反射訪問導出、未導出的類型的私有域 exports com.api;// 未導出包在編譯時依舊是不可訪問的 } module api{ exports com.api; opens com.api; }
-
6.現(xiàn)有代碼使用Java9
將現(xiàn)有代碼遷移到Java9可以分兩步:
在Java9上構建和運行現(xiàn)有代碼,無需模塊化
Java9有一個特殊的未命名模塊(unnamed module),該模塊可以讀取其他所有的模塊。在模塊之外編譯和加載的代碼都在未命名模塊中。
-
由于JDK是模塊化的,對類進行了強封裝,通過反射訪問這些類型時,會有警告。
(java9中exports導出的公共類型,反射訪問該類型的私有域是禁止的,為了保持兼容性,默認運行時訪問可以通過java選項--illegal-access=值,控制,默認值為permit )
編譯和運行未命名模塊時,以java.se作為根模塊,如果程序使用了java.se.ee下的模塊,會報錯??梢允褂?-add-modules添加相關模塊。
- 如果代碼使用了已經刪除的類型,改代碼??梢允褂肑DK附帶的一個工具jdeps查找在使用的已經被刪除或封裝的JDK類型。
將代碼模塊化
其實規(guī)模比較小的程序,還有比較老的系統(tǒng)(沒有太多更新,只是維護改bug)沒必要模塊化。程序規(guī)模比較大而且更新比較多的話,可以考慮遷移,因為模塊化是可以提高可維護性和可重用性的。
將現(xiàn)有代碼模塊化最大的問題:如何遷移第三方庫,大部分第三方庫都只是普通Jar文件,不是模塊。
-
創(chuàng)建自動模塊:將現(xiàn)有jar文件放到模塊路徑,不用改變任何內容,就可以創(chuàng)建一個自動模塊
- 自動模塊會導出所有包
- requires transitive 所有其他已解析的模塊
添加該第三方庫的包到被依賴的模塊描述文件中
總結
模塊化的好處
- 模塊描述文件,編譯時就能檢查模塊所有依賴,減少運行時錯誤
- 強封裝,模塊需要顯示的配置向其他模塊導出的內容,內部實現(xiàn)細節(jié)完全不可見
- 提高可維護性
- 提高安全性