Jigsaw

Java 9 已經決定在2017/9/21釋出,其中最大的改變來自於jigsaw,良葛格有解釋為什麼Java 9要採用模組化,慢慢地,我們也要從熟悉的classpath轉移到 module。
瞭解一項新東西,最好的方法就是從範例開始, 所以建議從 Quick Start Guide開始做起,然後再回頭去看 The State of the Module SystemThe State of the Module System,最後再看一下又修正了什麼,大概就完整把圖拼起來了。
因為目前Java 9正式版尚未釋出,所以我打算先用VM來作,首先裝一個Cent 7的環境,然後再安裝OpenJDK相關的Library
$wget https://copr.fedorainfracloud.org/coprs/omajid/openjdk9/repo/epel-7/omajid-openjdk9-epel-7.repowget
$sudo cp omajid-openjdk9-epel-7.repo /etc/yum.repos.d/cp omajid-openjdk9-epel-7.repo /etc/yum.repos.d/
$yum -y install java-9-openjdk-devel java-9-openjdk-jmods
...
$ls -l /usr/bin/java /usr/bin/javac /usr/bin/jlink /etc/alternatives/java /etc/alternatives/javac /etc/alternatives/jlink
lrwxrwxrwx. /usr/bin/java -> /etc/alternatives/java
lrwxrwxrwx. /usr/bin/javac -> /etc/alternatives/javac
lrwxrwxrwx. /usr/bin/jlink -> /etc/alternatives/jlink
lrwxrwxrwx. /etc/alternatives/java -> /usr/lib/jvm/java-9-openjdk-9.0.0.163-1.el7.centos.x86_64/bin/java
lrwxrwxrwx. /etc/alternatives/javac -> /usr/lib/jvm/java-9-openjdk-9.0.0.163-1.el7.centos.x86_64/bin/javac
lrwxrwxrwx. /etc/alternatives/jlink -> /usr/lib/jvm/java-9-openjdk-9.0.0.163-1.el7.centos.x86_64/bin/jlink
我們先照Start Guide先來第一個專案
$mkdir -p src/com.greetings/com/greetings
//建立程式碼後顯示
$cat src/com.greetings/module-info.java
module com.greetings { }
$cat src/com.greetings/com/greetings/Main.java
package com.greetings;
public class Main {
   public static void main(String[] args) {
       System.out.println("Greetings!");
   }
}
//編譯後的class放到這
$mkdir -p mods/com.greetings
//編譯目錄下所有找得到的java檔案,如果你是用Windows或是不要用find找出所有java檔,那就一個個寫出java檔的路徑檔案並用空白隔開
$javac -d mods/com.greetings $(find -name "*.java")
//執行程式
$java --module-path mods -m com.greetings/com.greetings.Main
Greetings!
這裡明顯以 --module-path (或是用-p)取代以前的 classpath,而且必須清楚指明要執行哪個 模組下/的類別,所以Java9以後的專案,都必須在 default package的位置放一個 module-info.java的檔案,以宣示該目錄向下的package(在此是 com.greetings)都是屬於這個模組(此例是 com.greetings)。
如果你有時間,可以試著在 module-info.java 下的 package 再另外放一個 module-info.java,基本上你會得到一個too many module declarations found的錯誤,因為default package位置的 module-info.java已經包含了所有下面的package了。
再來第二個範例由上述範例升級而來
$mkdir -p src/org.astro/org/astro/
$cat src/org.astro/module-info.java
module org.astro {
   //指示參考此模組的程式可以使用 org.astro package下的型別
   exports org.astro;
}
$cat src/org.astro/org/astro/World.java
package org.astro;
public class World {
   public static String name() {
        return "world";
    }
}
$cat src/com.greetings/module-info.java
module com.greetings {
   //指示本模組會參考另一個模組 org.astro,不指定則無法參考org.astro的型別
   requires org.astro;
}
$cat src/com.greetings/com/greetings/Main.java
package com.greetings;
import org.astro.World;
public class Main {
   public static void main(String[] args) {
        System.out.format("Greetings %s!%n", World.name());
    }
}
$mkdir mods/org.astro
$javac -d mods/org.astro $(find src/org.astro/ -name "*.java")
$javac -p mods -d mods/com.greetings $(find src/com.greetings/ -name "*.java")
$java -p mods -m com.greetings/com.greetings.Main
Greetings world!
這裡很明顯模組 com.greetings參考了模組 org.astro,所以對com.greetings模組來說,不管是編譯或是執行都必須用(-p)指明模組的參考路徑(相當舊式用classpath),當用 -p 指明了模組所在(若參考到多個路徑,可以用;(windows)或:(非windows)隔開),則編譯或執行就會在 -p 所指示的路徑下找次目錄或Jar檔,並解讀module-info,直到找到 org.astro 的模組的所在為止。
在第2個例子,我們分別編譯 org.astro與com.greetings,以下用--module-source-path 指到所有相關模組的源碼所在並一起編譯
$javac -d mods --module-source-path src $(find src -name "*.java")
以下我們分別把兩個模組打包成jar檔
$mkdir mlib
//-C參數的說明是”變更為指定目錄並包含”,”mods/org.astro .”就是打包mods/org.astro下所有的檔案
$jar --create --file=mlib/org.astro@1.0.jar --module-version=1.0 -C mods/org.astro .
$jar --create --file=mlib/com.greetings.jar --main-class=com.greetings.Main -C mods/com.greetings .
$ls mlib
com.greetings.jar  org.astro@1.0.jar
//直接執行Jar的程式
$java -p mlib -m com.greetings
Greetings world!
因為打包 com.greetings modules時已經指定主要類別,所以這次執行的 -m 參數只有指定module而沒有指定main class。
以前Java靠classpath去尋找然後載入相關的型別,現在則是透過 module-info的資訊來尋找載入相關的型別,現在則是靠解讀 module-info 來解讀Library之間的相依關係。
讓我們解讀一下兩個Jar檔的module-info
//檢視模組內容
$jar --describe-module --file=mlib/org.astro@1.0.jar
module org.astro@1.0 (module-info.class)
 requires mandated java.base
  exports org.astro
//注意以下contains com.greetings但未export,所以即使其它模組參考此模組也無法知道com.greetings內的型別
$jar -d -f mlib/com.greetings.jar
module com.greetings (module-info.class)
 requires mandated java.base
  requires org.astro
  contains com.greetings
  main-class com.greetings.Main
多了一個 java.base,是的,所有module預設一定要參考到java.base這個module,你也可以檢視一下 java.base包含了那些東西
$java --list-modules java.base
module java.base@9-ea
 exports java.io
  exports java.lang
  exports java.lang.annotation
 …
 contains sun.util.spi

Service

模組化的另一個影響是 SPI(Service Provider Interface),最簡單的例子就是連到資料庫,雖然Jdbc 提供了相關資料庫使用的API,但實際上還得配合各家的 driver 才能正確地連線到各家資料庫,這就是SPI最常的應用,前述的JDBC就是Service的部分,資料庫Driver則是Provider,由之前的範例來看,package 必須從A模組 export 後,然後B模組宣告requires A模組才能使用A模組的型別,顯然這種強耦合的方式無法適用JDBC的情況,因為 JDBC 無法事先知道要配點那家的資料庫,所以也無法事先宣告requires 那家資料庫模組,所以模組必須加入Service/Provider的宣告方式。
Start Guide的範例如下:Class Diagram0.png
com.socket 代表 Service,而org.fastsocket則是Provider,當然Provider是可以抽換地。
$mkdir -p src/com.socket/com/socket/spi src/org.fastsocket/org/fastsocket/ mlib mods
//這裡開始是 com.socket的程式
$cat src/com.socket/module-info.java
module com.socket {
   exports com.socket;
    exports com.socket.spi;
   //宣告此模組為Service,需要Provider模組實做NetworkSocketProvider
    uses com.socket.spi.NetworkSocketProvider;
}
$cat src/com.socket/com/socket/NetworkSocket.java
package com.socket;

import java.io.Closeable;
import java.util.Iterator;
import java.util.ServiceLoader;

import com.socket.spi.NetworkSocketProvider;

public abstract class NetworkSocket implements Closeable {
   protected NetworkSocket() { }
    public static NetworkSocket open() {
        ServiceLoader<NetworkSocketProvider> sl
            = ServiceLoader.load(NetworkSocketProvider.class);
        Iterator<NetworkSocketProvider> iter = sl.iterator();
        if (!iter.hasNext())
            throw new RuntimeException("No service providers found!");
        NetworkSocketProvider provider = iter.next();
        return provider.openNetworkSocket();
    }
}
$cat src/com.socket/com/socket/spi/NetworkSocketProvider.java
package com.socket.spi;

import com.socket.NetworkSocket;

public abstract class NetworkSocketProvider {
   protected NetworkSocketProvider() { }

   public abstract NetworkSocket openNetworkSocket();
}
//以下是org.fastsocket實做Provider
$cat src/org.fastsocket/module-info.java
module org.fastsocket {
   requires com.socket;
   //宣告本模組以FastNetworkSocketProvider實做spi.NetworkSocketProvider
    provides com.socket.spi.NetworkSocketProvider
        with org.fastsocket.FastNetworkSocketProvider;
}
$cat src/org.fastsocket/org/fastsocket/FastNetworkSocket.java
package org.fastsocket;

import com.socket.NetworkSocket;

class FastNetworkSocket extends NetworkSocket {
   FastNetworkSocket() { }
    public void close() { }
}
$cat src/org.fastsocket/org/fastsocket/FastNetworkSocketProvider.java
package org.fastsocket;

import com.socket.NetworkSocket;
import com.socket.spi.NetworkSocketProvider;

public class FastNetworkSocketProvider extends NetworkSocketProvider {
   public FastNetworkSocketProvider() { }

    @Override
    public NetworkSocket openNetworkSocket() {
        return new FastNetworkSocket();
    }
}
//以下是測試程式碼
$cat src/com.greetings/module-info.java
module com.greetings {
   requires com.socket;
}
$cat src/com.greetings/com/greetings/Main.java
package com.greetings;

import com.socket.NetworkSocket;

public class Main {
   public static void main(String[] args) {
        NetworkSocket s = NetworkSocket.open();
        System.out.println(s.getClass());
    }
}
//編譯全部程式
$javac -d mods --module-source-path src $(find src -name "*.java")
//執行程式
$java -p mods -m com.greetings/com.greetings.Main
class org.fastsocket.FastNetworkSocket
上述可以看出com.socket模組完全不知道 org.fastsocket模組,能正確執行,完全靠得是由modue-path內解析各目錄或Jar內的moudle-info找到匹配的模組來執行。
現在我們把 org.fastsocket打包成jar然後再把module-path的 org.fastsocket移除後再來測試執行
$jar --create --file=mlib/org.fastsocket.jar -C mods/org.fastsocket .
$ll mlib
-rw-r--r--. 1 root root 1648  org.fastsocket.jar
$rm -rf mods/org.fastsocket/
//再次執行程式會因為沒有Provider而失敗
$java -p mods -m com.greetings/com.greetings.Main
Exception in thread "main" java.lang.RuntimeException: No service providers found!Exception in thread "main" java.lang.RuntimeException: No service providers found!
   at com.socket/com.socket.NetworkSocket.open(NetworkSocket.java:17)
   at com.greetings/com.greetings.Main.main(Main.java:7)
//把Provider放入 module-path而再次執行成功(Windows環境要把冒號換成分號)
$java -p mods:mlib/org.fastsocket.jar -m com.greetings/com.greetings.Main
class org.fastsocket.FastNetworkSocket

Linker

因為模組化的方式,我們可以透過工具將Java環境切出一個最小執行單位。以下範例繼續延用Service的例子。
//設定JAVA_HOME環境變數
$export JAVA_HOME=/usr/lib/jvm/java-9-openjdk-9.0.0.163-1.el7.centos.x86_64
//以下module-path必須把JAVA本身的模組加入
$jlink -p $JAVA_HOME/jmods:mods --add-modules com.greetings --output greetingsapp
我們現在有了一個greetingsapp的目錄,現在我們把整個目錄搬到另一台沒有Java 9的電腦環境(這裡當是是另一台Linux,我的環境是Fedora 20).
//檢視一下整個JAVA環境剩下那些模組
F20@greetingsapp$./bin/java --list-modules
com.greetings
com.socket
java.base@9-eacom.greetingscom.socketjava.base@9-ea
//看一下整個執行環境大小
F20@greetingsapp$du -sh
388M    .
F20@greetingsapp$./bin/java -m com.greetings/com.greetings.Main
Exception in thread "main" java.lang.RuntimeException: No service providers found!
   at com.socket/com.socket.NetworkSocket.open(NetworkSocket.java:17)
   at com.greetings/com.greetings.Main.main(Main.java:7)
發生錯誤,所以我們把之前產生的org.fastsocket.jar搬過來,再執行一次
F20@greetingsapp$ls -l org.fastsocket.jar
-rw-r--r--. 1 root root 1648  org.fastsocket.jar
F20@greetingsapp$./bin/java -p org.fastsocket.jar  -m com.greetings/com.greetings.Main
class org.fastsocket.FastNetworkSocket
當然我們一開始也可以把 Provider 加入linker,不過就會變成服務綁定,以後有了更好的實做就不好抽換了,回到Java9
//之前已經刪除mods/org.fastsocket只剩org.fastocket.jarmlib目錄內
$jlink -p $JAVA_HOME/jmods:mods:mlib --add-modules com.greetings --bind-services --output greetingsapp
把整個greetingsapp再搬到另一台機器
//沒有ProviderJar檔也能執行
F20@greetingsapp$./bin/java -m com.greetings/com.greetings.Main
class org.fastsocket.FastNetworkSocket
Start Guide到此結束,我們要回頭看看Jigsaw的詳細內容

The State of the Module Systeml

原文講的很詳細,但你可能沒時間去看,所以我把重點照我的想法列出。
有個模組長這樣(在前面的部分有提到 module-info.java放在default package 的位置)
module com.foo.bar{
   requires org.baz.qux;
   exports com.foo.bar.alpha;
   exports com.foo.bar.beta;
}
可以想成這個模組是由一家 foo.com 公司的 bar 部門所開發,所以她們把模組名稱定義為 com.foo.bar。
但是這個模組會用到其他人開發的模組,也許是來自其它部分給的 class 目錄或是Jar檔,反上這個叫 org.baz.qux的模組在module-path一定可以找到,不然這個com.foo.bar的模組不要說執行,甚至連compile都不能過。
bar部們開發的是函式庫,當然要把一些API用到的型別公開給其他人或模組使用,所以公開了 com.foo.bar.alpha 與 com.foo.bar.beta 兩個package,涉及一些演算法的部分可能放在 com.foo.bar.internal內,當然這商業機密就不適合公開了。
module-info.class的內容記載著這個模組資訊,所以很重要,無論是 compile time或是run time都必須能在 module-path 找得到它,所以它又被稱為observable modules(看得到的模組)。
好了,函式庫就是拿來用的,現在有一個新的app模組要用到 com.foo.bar的一個class,叫做com.for.bar.alpha.Go,所以我們必須在這個模組加以宣告
module com.foo.app {
   requires com.foo.bar;
   requires java.sql;
}
除了用到com.foo.bar外,這app也會存取資料庫,用 java --list-modules java.sql 看一下java.sql模組的內容
module java.sql {
   requires java.logging;
    requires java.xml;
    exports java.sql;
    exports javax.sql;
    exports javax.transaction.xa;
}
看到java.sql又會用到 java.login與java.xml兩個模組,透過解析模組的方式,JAVA執行環境最終能解出整個module graphm1.png
深色表示有明確的requires宣告,視為顯式相依(explicit dependence
relationships),沒有明確宣告而會實際參考者,例如所有模組預設一定會參考到java.base模組視為隱式相依(implicit dependences)。

Readability

上面 com.foo.app 可以讀取 com.foo.bar 裡的com.foo.bar.alpha.Go,所以稱做 com.foo.bar 可被com.foo.app讀取(readable)。但是請注以 org.baz.qux雖然可被com.foo.bar所讀取,但卻不可以被 com.foo.app 所讀取。原文有一句話很重要,單獨列出:
The readability relationships defined in a module graph are the basis of
reliable configuration: The module system ensures that every dependence
is fulfilled by precisely one other module, that the module graph is
acyclic, that every module reads at most one module defining a given
package, and that modules defining identically-named packages do not
interfere with each other.
模組系統保證package只會從一個模組讀取,不同的模組也許會export出相同名稱的package,只要package不要互相干擾:例如A與B模組export出同名package,只要A、B互不相依且沒有模組同時相依於A與B就不會互相干擾(參考StackOverFlow)。

Accessibility

com.foo.app 可讀取 com.foo.bar
com.foo.bar是公開的library,且export 出它的package
因此com.foo.app可以存取com.foo.bar export出的package中的public class,例如com.foo.bar.alpha.Go可以被com.foo.app的程式所存取,但com.foo.app無法取com.foo.bar的私有欄位或是未export的package,此種方式稱為accessibility(存取性)。
跟Readability不同地是,Readability著重在module graph的關係,Accessibility則基於Readability之上,強調類別之間的存取限制,不符規定者,即使不同class來自同一個Class Loader也不能互相存取,原本把這種行為稱為Strong encapsulation
m2.png
從上圖看, com.foo.bar 可以存取 org.baz.qux內的public class,com.foo.app可以存取 com.foo.bar.alpha與com.foo.bar.beta package 內的所有public class,但是com.foo.app不可以無法讀取 org.baz.qux模組,所以也無法存取 org.baz.qux的任何package與class。

Implied readability

上述的accessibiltity可能會造成一種困擾,例如Interface java.sql.Driver內有一個函式樣式(Signature)如下:
pubic Logger getParentLogger();
Logger所屬的package被宣告在 java.logging 模組內,因為com.foo.app沒有宣告 requires java.loggin,根據readability與accessibiltity,com.foo.app沒有理由會認得 Logger是什麼東西。
但這會造成困擾,表示com.foo.app的程式這樣寫會造成問題(因為無法存取Logger)
String url = ...;
Properties props = ...;
Driver d = DriverManager.getDriver(url);
Connection c = d.connect(url, props);
d.getParentLogger().info("Connection acquired");
解決的方法就是讓com.foo.app可以自動 requres java.logging。所以java.sql內的module-info宣告如下:
module java.sql {
   requires transitive java.logging;
   requires transitive java.xml;
   exports java.sql;
   exports javax.sql;
   exports javax.transaction.xa;
}
讓所有requires java.sql的模組都自動可讀取 java.loggin與java.xml,此稱Implied readability。所以原有的module graph變更如下
m3.png
上圖加了兩根黃綠色棒的requires關係就是Implied readability,也就是java.logging與java.xml兩個模組可被com.foo.app模組所讀取,所以com.foo.app裡的程式可以存取 java.loggin與javax.xml packge內的public class。

The unnamed module

Java多年來已經建立了數以十萬計的library,顯然不會因為Jigsaw而放棄,所以一定要有解決的方案,所以Java 9 仍會延用 classpath,而且所有 classpath內的library都會自動被歸類到 unnamed module。
unnamed module預設 export library內的所有package,而且所有模組對unnamed module都是可讀取的(readable),例如,你有一個 old-fashion.jar 在classpath上,old-fashion內的程式絕對可以讀取com.foo.bar.alpha與com.foo.bar.beta內的public class;

m4.png
上圖顯示3個jar放在classpath上,則對所有named module具有Implied readability。
雖然 unnamed module export出所有package,但不代表你的 module可以存取unnamed module中的class(後面會解釋如何access unamed module),因為若是開放named module可以存取 unmaed module,最終會造成混亂。
另外,若是Library同時存在於 module-path與classpath,則classpath會被忽略,只會從module-path載入類別。

Automatic modules

前面提到 named module無法讀取unnamed module ,所以我們的模組程式必須要有特別的方式讀取這些unamed module。
在上面Accessibility那節提到 com.foo.app requires com.foo.bar requires org.baz.qux,原本3個程式都是在Java 9之前所建立,因為 com.foo是你公司自行開發,所以很順利升級並改寫為module,但是org-baz-qux.jar不是你公司的產品也沒有程式碼,咋辦?
此時,我們就把org-bax-qux.jar 放到 module-path,然後就有了一個叫做 org.baz.qux 的module,此模組稱為Automatic module(沒有定義org.baz.qux的module-info卻成了named module),然後在com.foo.bar的module-info宣告
module com.foo.app {
   requires org.baz.qux;
   export com.foo.bar.alpha;
   export com.foo.bar.beta;
}
因此com.foo.bar則具備對org.baz.qux的可讀取性
m5.png
因為沒有任何方法可以知道automatic module相依於那些模組,所以一旦建立module graph,所以Automatic module可讀取named modue或是unnamed module:
m6.png
從上圖看,任何module對org.baz.qux都是可讀取。
也因為如此,沒有任何方式可知道那些類別會被其它模組使用,所以automatic module的所有類別都會被export
m78.png
最後也由於沒有任何方式可以知道一個automatic module的函式樣式(Signature)是否參考到來自其它automatic module內的type,例如下圖只有com.foo.app升級為Jigsaw,com.foo.bar與org.baz.qux仍然作為automatic module的情況下,得到下圖:
m8.png
除非解讀這兩個Jar內的所有Class,否則無法知道是否com.foo.bar內的某個函式回傳org.baz.qux內的型別,所以automatic module必須可以讀取其它的automatic module:
m9.png
所以盡管我們知道com.foo.app根本沒有存取com.foo.app內的class,但現在com.foo.app內的code確實可以存取來自org.baz.qux的class。
以下是在Oracle JDK9出了以後新增的測試
首先我先把Java切換到之前的版本(7或8都可以),找個地方建立以下程式
$cat Greeting.java
package old.fashion;
public class Greeting {
   public String to(String some) {
       return "Hello " + some;
   }
}
$javac -d . Greeting.java
$jar cf old-fashion.jar old
現在我們有了一個old-fashion.jar,我們把它搬到JDK9的環境,看看它是如何變為automatice module
$jar -d -f ./old-fashion.jar
找不到模組描述區。已自動衍生模組。

old.fashion automatic
requires java.base mandated
contains old.fashion
#我們準備目錄如下:
$tree
.
├── mods
│   └── old-fashion.jar
└── src
   └── com.greetings
       ├── com
       │   └── greetings
       │       └── Main.java
       └── module-info.java
$cat src/com.greetings/module-info.java
module com.greetings {
   requires old.fashion;
}
$cat src/com.greetings/com/greetings/Main.java
package com.greetings;
import old.fashion.Greeting;

public class Main {
    public static void main(String[] args) {
        System.out.println(new Greeting().to("World!"));
    }
}
#編譯程式
$javac -p mods -d mods --module-source-path src $(find src -name "*.java")
#以module方式執行
$java -p mods -m com.greetings/com.greetings.Main
Hello World!
#以classpath方式執行(也可以試著把jar檔搬到其它地方)
$java -cp mods/old-fashion.jar:mods/com.greetings com.greetings.Main
Hello World!
另外要注意的是 jlink無法應用在automatic module。

Bridges to the class path

不是所有既有的Jar都能成為aotomatic module,例如兩個包含相同名稱的package,模組系統保證package只會從一個模組讀取(參考上面那段單獨列出的原文),應避免讓兩都都成為automatic module,必須捨棄其中一個或是把兩個都保留於classpath。
因為automatic module對named module與unnamed module都具有讀取能力,所以automatic moudle可以做為顯式模組與classpath溝通的橋樑。例如:classpath裡同時有org-baz-fiz.jar與org-baz-fuz.jar,則得到以下的module graph:
m10.png
之前有說過,unamed module export出其所有package,所以automatic module可以讀取來自classpath的所有class,但是requires這個automatic module的顯式模組因為無法相依於unamed moudle所以無法使用來自classpath的型別,例如com.foo.app使用了com.foo.bar的型別,且其函式樣式(Signature)回傳了來自classpath jar的型別,所以com.foo.app無法存取這個回傳型別(因為顯式模組無法相依於unamed module),補救的方法就是把com.foo.app降為automatic module,直到其它相關的Jar可做為automatic module或是改寫為顯式模組。

Services

之前的範例已展示的很清楚,以mysql來說,就是透過observable modules建立整個module graph
m11.png
java.sql的模組顯示如下:
module java.sql {
   requires public java.logging;
   requires public java.xml;
   exports java.sql;
   exports javax.sql;
   exports javax.transaction.xa;
   uses java.sql.Driver;
}
不是說老式的Driver不能用,module system一樣可透過掃瞄META-INF/Services知道Provider,至於採用module的表示法如下:
module com.mysql.jdbc {
   requires java.sql;
   requires org.slf4j;
   exports com.mysql.jdbc;
   provides java.sql.Driver with com.mysql.jdbc.Driver;
}
然後官方就解釋這樣比較優:效率高, 編譯時期檢查bla..bla..
最後要說的就是一個automatic module jar如果包含 META-INF/services 的老方式,會被視同模組宣告了對應的provider,而automatic module可以存取所有的Service。

Reflection

為了在執行時期可以經由reflection解讀module, 所以導入的新的package:java.lang.module,可經由Class::getModule(),取得 Module class以利用它的方法取得各種資訊。
java.lang.modul不止在執行時期用到,同時也支編譯時期與文件工具處理。
看到這裡,可能你會想到前面的存取性的問題,憑什麼reflection可以直接存取module內的資訊?我們可以參考一下這裡所提的Open Module
open module com.foo.bar{
   requires org.baz.qux;
   exports com.foo.bar.alpha;
   exports com.foo.bar.beta;
}
這樣子宣告讓 refection 框架可以在執行時期(編譯時期不行)透過refelction API存取模組內部,即使是非public的成員。
若是非的限定Reflection的存取範圍,可以這樣宣告:
module com.foo.bar{
   requires org.baz.qux;
   opens com.foo.bar.alpha;
   opens com.foo.bar.beta to com.foo.bar.app;
   exports com.foo.bar.alpha;
   exports com.foo.bar.beta;
}
則Relection API只能在執行時期存取 com.foo.bar.alpha的內部。
opens <packages> to <module> 則限定那個模組可以再執行時期Rellfect com.foo.bar.beta套件。

Reflective readability

結論是
the reflection API simply to assume that any code that reflects upon some type is in a module that can read the module that defines that type.
reflect某種型別這段代碼所存在的模組必可以讀取包含該型別的模組,以下是我的理解
reflect.png
以確保舊時像下面的Code還能運作
String providerName
   = System.getProperty("javax.xml.stream.XMLInputFactory");
if (providerName != null) {
   Class providerClass = Class.forName(providerName, false,
                                       Thread.getContextClassLoader());
   Object ob = providerClass.newInstance();
   return (XMLInputFactory)ob;
}
// Otherwise use ServiceLoader
...

Class loaders、Unnamed modules與Layers

跳過,有興趣者看原文,或者也可參考這裡

Qualified exports

有時侯,模組內的某些package並不想隨便讓其它模組所知道,所以要加以限制。例如Java.base的sun.reflect package只針對某些模組進行export。
module java.base {
   ...
   exports sun.reflect to
       java.corba,
       java.logging,
       java.sql,
       java.sql.rowset,
       jdk.scripting.nashorn;
}
因此Accessibility的定義重新說明如下:
com.foo.app 可讀取 com.foo.bar
com.foo.bar是公開的library,且export 出它的package或是export給com.foo.app
所以Module class有一個方法可以檢測某個package是否export給target模組
public final class Module {
   ...
   public boolean isExported(String packageName, Module target);
}

註:
另外看到這篇提到
    requires static <module>
static 關鍵字表示在編譯時期必須檢查相依關係,但在執行時期則不一定必須(optional)

留言

這個網誌中的熱門文章

企業人員的統一管理-FreeIPA學習筆記

證交所最佳五檔的程式解析

Postgresql HA