天天看點

JDK9的新特性:JPMS子產品化

目錄

  • ​​簡介​​
  • ​​JDK9中子產品的實作​​
  • ​​JDK中的module​​
  • ​​建立自己的module​​
  • ​​深入了解module-info​​
  • ​​transitive​​
  • ​​static​​
  • ​​exports to​​
  • ​​open pacakge​​
  • ​​provides with​​
  • ​​總結​​

JDK9的新特性:JPMS子產品化

簡介

JDK9引入了一個新的特性叫做JPMS(Java Platform Module System),也可以叫做Project Jigsaw。子產品化的本質就是将一個大型的項目拆分成為一個一個的子產品,每個子產品都是獨立的單元,并且不同的子產品之間可以互相引用和調用。

在module中會有中繼資料來描述該子產品的資訊和該子產品與其他子產品之間的關系。這些子產品組合起來,構成了最後的運作程式。

聽起來是不是和gradle或者maven中的子產品很像?

通過元件化,我們可以根據功能來區分具體的子產品,進而保持子產品内的高聚合,子產品之間的低耦合。

另外,我們可以通過子產品化來隐藏接口的具體實作内容,進而不影響子產品之間的調用。

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

JDK9中子產品的實作

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

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

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

JDK9的新特性:JPMS子產品化

其中java.base子產品比較特殊,它是獨立的子產品,這就意味着它并不依賴于其他任何子產品,并且java.base是其他子產品的基礎,是以在其他子產品中并不需要顯式引用java.base。

我們再總結一下:

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

一般來說使用子產品和不使用子產品對使用者來說基本上是感覺不到的,因為你可以将子產品的jar包當成普通的jar包來使用,也可以将普通的jar包當成子產品的jar包來使用。

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

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

  1. module中包含有一個module-info.class檔案,這個檔案定義了module本身的資訊和跟外部module之間的關系。
  2. 要使用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檔案就可以了。如下圖所示:

JDK9的新特性:JPMS子產品化

代碼其實很簡單,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的文法。

本文的例子​​https://github.com/ddean2009/

learn-java-base-9-to-20​​

本文作者:flydean程式那些事