列出自帶模塊:java --list-modules
mac多版本jdk共存:http://adolphor.com/blog/2016...
模塊規則示意圖:
html
incubator modules:孵化模塊 以jdk.incubator開頭,好比jdk.incubator.httpclient(jdk11以後這是正式的模塊了:[java.net.http][1]
,具體參考:http://openjdk.java.net/jeps/...java
module descriptor:模塊描述文件 module-info.java
例如java.prefs的模塊描述文件內容:git
module java.prefs{ requires java.xml; exports java.util.prefs; }
requires表明依賴的模塊,只有依賴的模塊存在才能經過編譯並運行.須要注意的是,全部模塊均自動隱式依賴java.base
模塊,不須要顯示聲明
exports指出須要暴露的包,若是某個包沒有被exports,那麼其餘模塊是沒法訪問的。github
Readability:指的是必須exports的包纔可被其餘模塊訪問
Accessibility:指的是即便是exports的包,其中的類的可訪問下也要基於java的訪問修飾符,僅有public修飾的纔可被其餘模塊訪問sql
Implied Readability(隱式Readability, requires transitive):
Readability默認狀況下是不會被傳遞的, 好比Maven中,咱們知道依賴能夠被傳遞,可是module的requires不會被傳遞,好比: 下圖中java.desktop沒法訪問java.xml模塊中exports的包,雖然它引用了java.prefs模塊
macos
可是若是你將requires改爲requires transitive
的話,那麼傳遞性依賴就能夠生效了。
有個時候咱們定義API模塊時常常忘記哪些該requires transitive,這時該怎麼辦呢? 模塊編譯時使用-Xlint:exports
選項,它會檢測出這些問題並warn。
Aggregate Module(聚合模塊)
因爲requires transitive
的存在,就能夠支持聚合模塊。有些聚合模塊能夠沒有任何代碼,就一個module-info.java描述文件,好比java.se, java.se.ee模塊
不建議直接引用java.se模塊,由於它就至關於java9之前版本的rt.jar的內容。windows
Qualified Exports(有限制的exports)
好比我只想exports某個包給部分模塊,而不是全部模塊api
exports com.sun.xml.internal.stream.writers to java.xml.ws,java.sql;
Qualified Exports不建議普通模塊使用,java platform使用它主要是爲了減小內部重複代碼,通常用它暴露internal內部使用的一些類給部分模塊。框架
module path
module path相似於classpath,只不過它只包括module
module resolution支持定義模塊依賴圖,根據root module去查詢。ide
在modular jdk中以非模塊化方式開發
在java9中也是容許你以非模塊化方式開發和運行應用的(也就是說,模塊化開發是可選的),若是你的應用中沒有module-info.java,那麼這就是一個unnamed module. java9對於unnamed module的處理方式就是全部的jdk模塊均直接可用(模塊圖中是以java.se模塊做爲root模塊的,也意味着單獨處於java.se.ee下的一些包,好比JAXB API是沒法訪問到的)。
可是須要注意的是,在java8以及以前的版本中,咱們能夠訪問jdk中的一些不推薦訪問的內部類,好比com.sun.image.codec.jpeg,但在java9模塊化以後被強封裝了,因此在java9中沒法使用這些內部類,也就是說沒法經過編譯,可是java9爲了保持兼容性,容許以前引用這些內部類的已有的jar或已編譯的類正確運行。換言之,就是java9不容許源碼中引用這些類,沒法經過編譯,可是以前版本中引用這些類的已編譯class文件是容許正常運行的。
MacBook-Pro:easytext-singlemodule tjw$ tree . ├── README.md ├── out │ └── easytext │ ├── javamodularity │ │ └── easytext │ │ └── Main.class │ └── module-info.class ├── run.sh └── src └── easytext ├── javamodularity │ └── easytext │ └── Main.java └── module-info.java
其中區別於傳統src目錄,模塊src目錄下首先是模塊目錄easytext,這名字與module-info.java裏的保持一致,模塊目錄下包含源碼及module-info.java模塊描述文件。src/easytext是源碼目錄, javamodularity/easytext是包
編譯文件run.sh內容:
mkdir -p out javac -d out --module-source-path src -m easytext java --module-path out -m easytext/javamodularity.easytext.Main
打包成jar
jar -cfe out/easytext.jar javamodularity.easytext.Main -C out/easytext .
-cf不用說了,-e指定入口類,-C指定須要打包進jar的文件路徑
運行模塊jar:
java --module-path out -m easytext
請注意,這與直接運行模塊目錄的區別:java --module-path out -m easytext/javamodularity.easytext.Main, 運行模塊jar因爲咱們打包時經過-e選項指定了入口類,這時-m只須要指定模塊名便可運行。
若是你想查看運行時模塊的加載過程:java --show-module-resolution --limit-modules java.base --module-path out -m easytext
輸出結果: 表示easytext爲root模塊,因爲我限制了java.base再也不往下輸出了,而咱們模塊又沒有別的額外依賴,因此僅有這行輸出。
root easytext file:///Users/tjw/learn/java-9-moodularity-examples/chapter3/easytext-singlemodule/out/easytext.jar
假如我去掉--limit-modules限制,運行,則輸出以下:java --show-module-resolution --module-path out -m easytext
root easytext file:///Users/tjw/learn/java-9-moodularity-examples/chapter3/easytext-singlemodule/out/easytext.jar java.base binds jdk.localedata jrt:/jdk.localedata java.base binds jdk.charsets jrt:/jdk.charsets java.base binds jdk.jlink jrt:/jdk.jlink java.base binds jdk.jartool jrt:/jdk.jartool java.base binds jdk.jdeps jrt:/jdk.jdeps java.base binds jdk.compiler jrt:/jdk.compiler java.base binds jdk.javadoc jrt:/jdk.javadoc java.base binds jdk.packager jrt:/jdk.packager java.base binds java.desktop jrt:/java.desktop java.base binds jdk.crypto.cryptoki jrt:/jdk.crypto.cryptoki java.base binds java.naming jrt:/java.naming java.base binds jdk.crypto.ec jrt:/jdk.crypto.ec java.base binds java.xml.crypto jrt:/java.xml.crypto java.base binds java.security.jgss jrt:/java.security.jgss java.base binds java.security.sasl jrt:/java.security.sasl java.base binds jdk.deploy jrt:/jdk.deploy java.base binds java.smartcardio jrt:/java.smartcardio java.base binds jdk.security.jgss jrt:/jdk.security.jgss java.base binds java.logging jrt:/java.logging java.base binds jdk.security.auth jrt:/jdk.security.auth java.base binds java.management jrt:/java.management java.base binds jdk.zipfs jrt:/jdk.zipfs jdk.security.auth requires java.security.jgss jrt:/java.security.jgss jdk.security.auth requires java.naming jrt:/java.naming jdk.security.jgss requires java.security.jgss jrt:/java.security.jgss jdk.security.jgss requires java.security.sasl jrt:/java.security.sasl jdk.security.jgss requires java.logging jrt:/java.logging jdk.deploy requires jdk.unsupported jrt:/jdk.unsupported jdk.deploy requires java.desktop jrt:/java.desktop jdk.deploy requires java.naming jrt:/java.naming jdk.deploy requires java.scripting jrt:/java.scripting jdk.deploy requires java.prefs jrt:/java.prefs jdk.deploy requires java.logging jrt:/java.logging jdk.deploy requires java.rmi jrt:/java.rmi jdk.deploy requires java.xml jrt:/java.xml jdk.deploy requires java.management jrt:/java.management java.security.sasl requires java.logging jrt:/java.logging java.security.jgss requires java.naming jrt:/java.naming java.xml.crypto requires java.logging jrt:/java.logging java.xml.crypto requires java.xml jrt:/java.xml java.naming requires java.security.sasl jrt:/java.security.sasl jdk.crypto.cryptoki requires jdk.crypto.ec jrt:/jdk.crypto.ec java.desktop requires java.datatransfer jrt:/java.datatransfer java.desktop requires java.xml jrt:/java.xml java.desktop requires java.prefs jrt:/java.prefs jdk.packager requires java.logging jrt:/java.logging jdk.packager requires java.desktop jrt:/java.desktop jdk.packager requires java.xml jrt:/java.xml jdk.packager requires jdk.jlink jrt:/jdk.jlink jdk.javadoc requires jdk.compiler jrt:/jdk.compiler jdk.javadoc requires java.xml jrt:/java.xml jdk.javadoc requires java.compiler jrt:/java.compiler jdk.compiler requires java.compiler jrt:/java.compiler jdk.jdeps requires jdk.compiler jrt:/jdk.compiler jdk.jdeps requires java.compiler jrt:/java.compiler jdk.jlink requires jdk.internal.opt jrt:/jdk.internal.opt jdk.jlink requires jdk.jdeps jrt:/jdk.jdeps java.prefs requires java.xml jrt:/java.xml java.rmi requires java.logging jrt:/java.logging java.management binds java.management.rmi jrt:/java.management.rmi java.management binds jdk.management.cmm jrt:/jdk.management.cmm java.management binds jdk.management.jfr jrt:/jdk.management.jfr java.management binds jdk.management jrt:/jdk.management java.management binds jdk.internal.vm.compiler.management jrt:/jdk.internal.vm.compiler.management java.scripting binds jdk.scripting.nashorn jrt:/jdk.scripting.nashorn java.naming binds jdk.naming.dns jrt:/jdk.naming.dns java.naming binds jdk.naming.rmi jrt:/jdk.naming.rmi java.datatransfer binds java.desktop jrt:/java.desktop java.compiler binds jdk.compiler jrt:/jdk.compiler java.compiler binds jdk.javadoc jrt:/jdk.javadoc jdk.naming.rmi requires java.rmi jrt:/java.rmi jdk.naming.rmi requires java.naming jrt:/java.naming jdk.naming.dns requires java.naming jrt:/java.naming jdk.scripting.nashorn requires java.logging jrt:/java.logging jdk.scripting.nashorn requires java.scripting jrt:/java.scripting jdk.scripting.nashorn requires jdk.dynalink jrt:/jdk.dynalink jdk.internal.vm.compiler.management requires jdk.internal.vm.ci jrt:/jdk.internal.vm.ci jdk.internal.vm.compiler.management requires jdk.internal.vm.compiler jrt:/jdk.internal.vm.compiler jdk.internal.vm.compiler.management requires jdk.management jrt:/jdk.management jdk.internal.vm.compiler.management requires java.management jrt:/java.management jdk.management requires java.management jrt:/java.management jdk.management.jfr requires jdk.jfr jrt:/jdk.jfr jdk.management.jfr requires java.management jrt:/java.management jdk.management.jfr requires jdk.management jrt:/jdk.management jdk.management.cmm requires jdk.management jrt:/jdk.management jdk.management.cmm requires java.management jrt:/java.management java.management.rmi requires java.management jrt:/java.management java.management.rmi requires java.naming jrt:/java.naming java.management.rmi requires java.rmi jrt:/java.rmi jdk.dynalink requires java.logging jrt:/java.logging jdk.internal.vm.compiler requires jdk.internal.vm.ci jrt:/jdk.internal.vm.ci jdk.internal.vm.compiler requires jdk.unsupported jrt:/jdk.unsupported jdk.internal.vm.compiler requires java.instrument jrt:/java.instrument jdk.internal.vm.compiler requires jdk.management jrt:/jdk.management jdk.internal.vm.compiler requires java.management jrt:/java.management jdk.internal.vm.ci binds jdk.internal.vm.compiler jrt:/jdk.internal.vm.compiler jdk.dynalink binds jdk.scripting.nashorn jrt:/jdk.scripting.nashorn
模塊路徑
模塊路徑格式支持三種:大目錄下面有多個模塊的情形,好比上述的out目錄;模塊目錄自己;模塊jar
模塊路徑分隔符,遵循系統的Path.sep;好比mac/Linux下是 :
,windows下是 ;
--module-path 能夠簡寫爲 -p 如 java -p
jlink
工具容許你建立一個運行時鏡像runtime image,包含java應用容許所需的最小集合。而不是像以前那樣須要打包整個jre.
$JAVA_HOME/bin/jlink --module-path out/:$JAVA_HOME/jmods \ --add-modules easytext \ --launcher easy=easytext \ --output easytext-image
須要注意的是,默認狀況下jlink沒有被添加到PATH中,須要你手動添加一下
跟javac和java命令不一樣,jlink須要指定jdk平臺的模塊路徑$JAVA_HOME/jmods
--add-modules 指定easytext爲root模塊
--launcher指定啓動的入口爲easytext模塊,easy是生成的啓動文件名
--output 指定生成的鏡像路徑
最終生成的鏡像文件內容:
easytext-image/ ├── bin/ │ ├── easy │ ├── java │ └── keytool ├── conf │ └── security │ └── policy │ ├── limited │ └── unlimited ├── include │ └── darwin ├── legal │ └── java.base └── lib ├── jli ├── security └── server
試了下,打包成壓縮文件後,只有12M大小:
-rw-r--r-- 1 tjw staff 12M 6 29 13:32 easy.tar.gz
查看已有模塊描述有兩種方式:
1、直接看module-info.java
2、使用命令 java --describe-module javafx.controls
注意:Jdk11已經將javafx從平臺模塊中移除
module easytext.gui { exports javamodularity.easytext.gui to javafx.graphics; requires javafx.graphics; requires javafx.controls; requires easytext.analysis; }
exports javamodularity.easytext.gui to javafx.graphics;是必須的,由於gui應用中Application會反射訪問具體的實現,那麼這就意味着javafx.graphics.Application的運行時須要可以訪問咱們的應用模塊的Application實現。
思考一個問題:如何確保模塊之間的解耦,經過定義一個接口模塊能夠分離實現,可是如何確保自動加載哪一個實現呢?
services的設計有點相似於ioc,概念上分爲服務提供者和服務消費者。
主要入口類就是java.util.ServiceLoader
,這個類在jdk6的時候就已經存在,不過在jdk9進行了改造以支持模塊化,jdk9以前ServiceLoader主要是用來使jdk更加插件化,一些框架好比dubbo也會使用ServiceLoader來作插件化工做。 jdk9以前services的提供是在jar包下的META-INF/services目錄下的一個文本文件,文件名爲服務接口的全限定類名,如:com.test.HelloWorld,文件內容也爲服務實現的全限定類名com.test.HelloWorldImpl。好比dubbo的filter
jdk9改造後使得ServiceLoader支持模塊化service加載,已達到模塊間面向接口,使實現解耦的目的。注意:服務提供模塊能夠不用exports服務實現。
步驟:
0、服務接口模塊定義
如模塊easytext.analysis.api
一、提供模塊描述中使用provides
module easytext.analysis.coleman { requires easytext.analysis.api; provides javamodularity.easytext.analysis.api.Analyzer with javamodularity.easytext.analysis.coleman.Coleman; }
二、消費模塊描述中使用uses
module easytext.cli { requires easytext.analysis.api; uses javamodularity.easytext.analysis.api.Analyzer; }
三、消費模塊代碼中使用ServiceLoader類加載
Iterable<Analyzer> analyzers = ServiceLoader.load(Analyzer.class); //其實ServiceLoader.load返回的是一個ServiceLoader實例,只不過它實現了Iterable接口 for (Analyzer analyzer: analyzers) { System.out.println(analyzer.getName() + ": " + analyzer.analyze(sentences)); }
ServiceLoader.load是懶加載的
ServiceLoader.load每調用一次都會返回一個ServiceLoader實例,獲取的服務實例也是新的,跟Spring等容器不一樣,它不存在單例模式。這就須要注意,千萬不要經過服務實例來共享狀態。
服務實現類要麼提供無參構造器,要麼提供public static provider
()方法,返回實例。
public static ExampleProviderMethod provider() { return new ExampleProviderMethod("Analyzer created by static method"); }
public interface Analyzer { String getName(); double analyze(List<List<String>> text); static Iterable<Analyzer> getAnalyzers() { return ServiceLoader.load(Analyzer.class); // <1> } }
module easytext.analysis.api { exports javamodularity.easytext.analysis.api; uses javamodularity.easytext.analysis.api.Analyzer; }
好處:ServiceLoader的調用及模塊uses聲明都統一在api模塊中定義。
問題: 上面所述的獲取服務接口實現的方式只能遍歷Itreable,而遍歷後全部的實現都會被初始化。兩個問題:一、我只關心某一個實現,如何標識獲取;二、我只想獲取某一個特定實現,但我不想在遍歷中初始化其餘實例;
問題1:我只關心某一個實現,如何標識獲取
方案1:在服務接口中添加標識方法,好比getName,而後消費者遍歷時經過getName的值來判斷。這樣不少場景下是可行的。可是也有些場景須要經過別的方式來標識,好比是否實現某個抽象類,是否被某個註解標註,這種狀況下就須要下面的方案2。
方案2: Java9對ServiceLoader API進行了強化,提供ServiceLoader.Provider stream
. ServiceLoader.Provider能夠在不實例化實現以前對實現類進行反射檢索。
好比下面的就是經過檢索被@Fast註解標註的實現。
public class Main { public static void main(String args[]) { ServiceLoader<Analyzer> analyzers = ServiceLoader.load(Analyzer.class); analyzers.stream() .filter(provider -> isFast(provider.type())) .map(ServiceLoader.Provider::get) .forEach(analyzer -> System.out.println(analyzer.getName())); } private static boolean isFast(Class<?> clazz) { return clazz.isAnnotationPresent(Fast.class) && clazz.getAnnotation(Fast.class).value() == true; } }
注意上述代碼中的provider.type()
,它返回的是實現類的Class對象,這裏咱們要注意,咱們的實現類是沒有被exports的,但經過provider.type()是能夠獲取到。可是咱們能夠調用provider.type().newInstance()嗎?不能夠,由於它仍然遵照模塊的封裝約定。若是強行調用會報IllegalAccessError異常。
對於Service,模塊解析方式和以前的相同:從root模塊開始,解析requires,而後解析uses,而後會把uses對應的provides模塊都解析到resolved module sets中。
**可是對於jlink鏡像打包而言,它不會把Service的provides模塊打包進去(一個直接的緣由就是java.base中使用了大量的uses),
因此使用jlink打包時須要注意經過--add-modules添加provides。** 固然若是你不知道有哪些provids模塊,能夠經過jlink選項 --suggest-providers 接口名
查看
$JAVA_HOME/bin/jlink --module-path mods/:$JAVA_HOME/jmods --add-modules main--suggest-providers javamodularity.easytext.analysis.api.Analyzer 建議的提供方: provider.factory.example provides javamodularity.easytext.analysis.api.Analyzer used by main provider.method.example provides javamodularity.easytext.analysis.api.Analyzer used by main
不過若是provide模塊也uses別的模塊,那麼也須要照樣分析,並根據須要--add-modules添加進來。
不過還有個替代方式,添加--bind-services
選項,添加後jlink會解析uses及provides,但不推薦使用,由於java.base也使用了大量uses,會致使打包後鏡像很大
源碼見書籍源碼:https://github.com/java9-modu...
主要參考書籍:《Java 9 Modularity Patterns and Practices for Developing Maintainable Applications》