詳解JDK9特性之JPMS模塊化

簡介

在module中會有元數據來描述該模塊的信息和該模塊與其他模塊之間的關系。這些模塊組合起來,構成瞭最後的運行程序。

聽起來是不是和gradle或者maven中的模塊很像?

通過組件化,我們可以根據功能來區分具體的模塊,從而保持模塊內的高聚合,模塊之間的低耦合。

另外,我們可以通過模塊化來隱藏接口的具體實現內容,從而不影響模塊之間的調用。

最後,我們可以通過顯示聲明來描述模塊之間的依賴關系。從而讓開發者更好的理解系統的應用邏輯。

JDK9中模塊的實現

在JDK9之前,java是通過不同的package和jar來做功能的區分和隔離的。

但在JDK9中,一切都發送瞭變化。

首先是JDK9本身的變化,JDK現在是以不同的模塊來區分的,如果你展開IDE中JDK的依賴,會看到java.base,java.compiler等模塊。

其中java.base模塊比較特殊,它是獨立的模塊,這就意味著它並不依賴於其他任何模塊,並且java.base是其他模塊的基礎,所以在其他模塊中並不需要顯式引用java.base。

我們再總結一下:

class是字段和方法的集合,package是class的集合,而module是package的集合。

一般來說使用模塊和不使用模塊對用戶來說基本上是感覺不到的,因為你可以將模塊的jar包當成普通的jar包來使用,也可以將普通的jar包當成模塊的jar包來使用。

當使用普通的jar包時,JDK將會采用一種Automatic modules的策略將普通jar包當成module jar包來看待。

那麼module和普通的jar包有什麼區別呢?

module中包含有一個module-info.class文件,這個文件定義瞭module本身的信息和跟外部module之間的關系。

要使用module jar包,需要將該jar包放入modulepath而不是classpath。

下面我們在具體的例子中詳細探索一下module的使用。

JDK中的module

剛剛講到瞭JDK也是以模塊來區分的,並且所有模塊的基礎都是java.base。我們可以使用java –list-modules 來列出現有的所有module:

java –list-modules 

[email protected]

[email protected]

[email protected]

[email protected]

[email protected]

[email protected]

[email protected]

[email protected]

….

也可以使用java –describe-module 來查看具體module的信息:

java –describe-module java.base

[email protected]

exports java.io

exports java.lang

exports java.lang.annotation

exports java.lang.constant

exports java.lang.invoke

exports java.lang.module

exports java.lang.ref

exports java.lang.reflect

exports java.lang.runtime

我們再具體看一下module-info.class文件中的內容:

module java.base {
    exports java.io;
    exports java.lang;
    exports java.lang.annotation;
    ...

看到瞭JDK自帶的module,接下來我們創建幾個自己的module來看看。

創建自己的module

假如我們有一個controller,一個service的接口,和兩個service的實現。

為瞭簡單起見,我們將這四個類分在不同的module中。

在IDEA中創建一個module很簡單,隻需要在java文件夾中添加module-info.java文件就可以瞭。如下圖所示:

代碼其實很簡單,Controller引用瞭Service接口,而兩個Service的實現又實現瞭Service接口。

看下controller和service兩個模塊的的module-info文件:

module com.flydean.controller {
    requires com.flydean.service;
    requires lombok;
}
module com.flydean.service {
    exports com.flydean.service;
}

requires表示該模塊所要用到的模塊名字。exports表示該模塊暴露的模塊中的包名。如果模塊不暴露出去,其他模塊是無法引用這些包的。

看下在命令行怎麼編譯,打包和運行module:

$ javac

    –module-path mods

    -d classes/com.flydean.controller

    ${source-files}

$ jar –create

    –file mods/com.flydean.controller.jar

    –main-class com.flydean.controller.ModuleController.Main

    -C classes/com.flydean.controller .

$ java

    –module-path mods

    –module com.flydean.controller

深入理解module-info

上一節我們將瞭module-info中的requires和exports。這一節我們來更多的講解module-info中的其他用法。

transitive

先看下modulea的代碼:

public ModuleService getModuleService(){
    return new ModuleServiceA();
}

getModuleService方法返回瞭一個ModuleService,這個ModuleService屬於模塊com.flydean.service,我們看下module-info的定義:

module com.flydean.servicea {
    requires com.flydean.service;
    exports com.flydean.servicea;
}

看起來好像沒什麼問題,但是如果有其他的模塊來使用servicea的getModuleService方法就會有問題,因為該方法返回瞭模塊com.flydean.service中定義的類。所以這裡我們需要用到transitive。

module com.flydean.servicea {
    requires transitive com.flydean.service;
    exports com.flydean.servicea;
}

transitive意味著所有讀取com.flydean.servicea的模塊也可以讀取 com.flydean.service。

static

有時候,我們在代碼中使用到瞭某些類,那麼編譯的時候必須要包含這些類的jar包才能夠編譯通過。但是在運行的時候我們可能不會用到這些類,這樣我們可以使用static來表示,該module是可選的。

比如下面的module-info:

module com.flydean.controller {
    requires com.flydean.service;
    requires static com.flydean.serviceb;
}

exports to

在module info中,如果我們隻想將包export暴露給具體的某個或者某些模塊,則可以使用exports to:

module com.flydean.service {
    exports com.flydean.service to com.flydean.controller;
}

上面我們將com.flydean.service隻暴露給瞭com.flydean.controller。

open pacakge

使用static我們可以在運行時屏蔽模塊,而使用open我們可以將某些package編譯時不可以,但是運行時可用。

module com.flydean.service {
    opens com.flydean.service.subservice;
    exports com.flydean.service to com.flydean.controller, com.flydean.servicea, com.flydean.serviceb;
}

上面的例子中com.flydean.service.subservice是在編譯時不可用的,但是在運行時可用。一般來說在用到反射的情況下會需要這樣的定義。

provides with

假如我們要在Controller中使用service的具體實現,比如servicea和serviceb。一種方法是我們直接在controller模塊中使用servicea和serviceb,這樣就高度耦合瞭。

在模塊中,我們可以使用provides with來對模塊之間的耦合進行解耦。

我們看下代碼:

module com.flydean.controller {
    uses com.flydean.service.ModuleService;
    requires com.flydean.service;
    requires lombok;
    requires slf4j.api;
}
module com.flydean.servicea {
    requires transitive com.flydean.service;
    provides com.flydean.service.ModuleService with com.flydean.servicea.ModuleServiceA;
    exports com.flydean.servicea;
}
module com.flydean.serviceb {
    requires transitive com.flydean.service;
    provides com.flydean.service.ModuleService with com.flydean.serviceb.ModuleServiceB;
    exports com.flydean.serviceb;
}

在controller中,我們使用uses來暴露要實現的接口。而在具體的實現模塊中使用provides with來暴露具體的實現。

怎麼使用呢?我們在controller中:

public static void main(String[] args) {
    List<ModuleService> moduleServices = ServiceLoader
    .load(ModuleService.class).stream()
    .map(ServiceLoader.Provider::get)
    .collect(toList());
    log.info("{}",moduleServices);
}

這裡我們使用瞭ServiceLoader來加載接口的實現。這是一種很好的解耦方式,這樣我可以將具體需要使用的模塊放在modulepath中,實現動態的修改實現。

要想在maven環境中順利完成編譯,maven-compiler-plugin的版本必須要在3.8.1以上。

總結

本文介紹瞭JDK9中module的使用,並在具體的中介紹瞭更多的module-info的語法。

以上就是詳解JDK9特性之JPMS模塊化的詳細內容,更多關於JDK9特性之JPMS模塊化的資料請關註WalkonNet其它相關文章!

推薦閱讀: