列出自带模块: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》