Java9模塊化學習

1. 什么是模塊化

本質上,模塊化就是將系統(tǒng)分解成獨立且相互連接的模塊的行為。java9模塊除了包含代碼外,一個重要特征是增加了描述模塊的文件:module-info.java

Q:為什么這么做?-- 減少復雜性

Project Jigsaw

Jigsaw 項目從2008就開始對JDk模塊化進行探索,2014年開始進行設計和實現(xiàn)。

2. 模塊化JDK

JDK由90個左右的平臺模塊組成,每個模塊都是一個定義好的功能塊,下圖顯示了部分模塊的子集以及依賴關系:


image.png
  1. 頂部的兩個重要模塊:java.se.ee,java.se,主要用于對其他模塊進行邏輯分組,屬于聚合器模塊。

  2. 每個模塊都隱式依賴于java.base,因為該模塊公開了java.lang, java.util之類的包。

  3. 模塊之間依賴關系沒有循環(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文件選擇正確的模塊版本并設置到模塊路徑上

image2.PNG

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添加相關模塊。

image3.PNG
  • 如果代碼使用了已經刪除的類型,改代碼??梢允褂肑DK附帶的一個工具jdeps查找在使用的已經被刪除或封裝的JDK類型。

將代碼模塊化

其實規(guī)模比較小的程序,還有比較老的系統(tǒng)(沒有太多更新,只是維護改bug)沒必要模塊化。程序規(guī)模比較大而且更新比較多的話,可以考慮遷移,因為模塊化是可以提高可維護性和可重用性的。

將現(xiàn)有代碼模塊化最大的問題:如何遷移第三方庫,大部分第三方庫都只是普通Jar文件,不是模塊。

  • 創(chuàng)建自動模塊:將現(xiàn)有jar文件放到模塊路徑,不用改變任何內容,就可以創(chuàng)建一個自動模塊

    • 自動模塊會導出所有包
    • requires transitive 所有其他已解析的模塊
  • 添加該第三方庫的包到被依賴的模塊描述文件中

總結

模塊化的好處

  • 模塊描述文件,編譯時就能檢查模塊所有依賴,減少運行時錯誤
  • 強封裝,模塊需要顯示的配置向其他模塊導出的內容,內部實現(xiàn)細節(jié)完全不可見
  • 提高可維護性
  • 提高安全性
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容