临时小驻

求仁得仁,复无怨怼。

模块化 Java 入门

2018-10-06 00:54:00 +0800

Java 9 的 Project Jigsaw 使 Java 引入了模块化系统(Modularity)。Jigsaw 是竖锯,意即切分 Java,十分形象。

模块是包(Package)之上的一层概念,将几个相关的 Java 包及其资源封装为一个模块,使其能够按需加载和使用。

模块有以下约定:

  1. 模块是代码和数据的集合
  2. 同一个包不能在多个模块中定义,因此也不能同时访问不同模块中同名的包。
  3. 模块不能循环依赖。
  4. 没有子模块的概念,即 java.sqljava.sql.rowset 是两个独立的包。

通过下述命令查看当前 JDK 的模块集合:

$ java --list-modules

java.base@11
java.compiler@11
java.datatransfer@11
java.desktop@11
java.instrument@11
java.logging@11
java.management@11
java.management.rmi@11
java.naming@11
java.net.http@11
java.prefs@11
java.rmi@11
java.scripting@11
java.se@11
java.security.jgss@11
java.security.sasl@11
java.smartcardio@11
java.sql@11
java.sql.rowset@11
java.transaction.xa@11
java.xml@11
java.xml.crypto@11
...

你能看到 java. 开头的标准模块,jdk. 开头的 JDK 特有模块。你还可能看到 javafx. 开头的 JavaFX 特有模块,oracle. 开头的 Oracle 特有模块等。每个模块尾部还有 @<ver> 指示模块属于哪个 Java 版本。

每个模块都需要提供提供 module-info.java 来描述模块信息。

module-info.java 形式一般如下:

[open] module <module-name> {
    <module-statement>;
    ...
}

<module-statement> 有以下几种:exports opens requires provides..with uses

exports opens 负责访问控制。

exports <package> [to <module1>, <module2>, ...]

对外导出当前模块的包,使其可以在编译时和运行时访问包内的 public 成员。

opens <package> [to <module1>, <moudle2>, ...]

对外打开当前模块的包,使其可以在运行时通过深层的反射访问包内的任何成员。

open 修饰的模块将 opens 其内的所有包。

requires 负责指明依赖。

requires [transitive] [static] <module-name>

static 表示被依赖模块只是在编译时必须存在,运行时不必须提供。
transitive 表示级联依赖,即:如果一个模块依赖了当前模块的话,那么它也将隐式依赖了 transivtive 的模块。

例如,java.desktop 模块里有 requires transitive java.xml,那么另一个模块 requires java.desktop 也会使其 requires java.xml

provides uses 提供了新的机制来分离服务接口与实现。

提供服务的模块添加以下声明:

providers <service-interface> with <service-impl-class1> [, , ..]`

使用服务的模块添加以下声明:

uses <service-interface>

便可以通过 java.util.ServiceLoader 来加载它:

List<MyService> observerFactories = ServiceLoader.load(MyService.class).stream()
        .map(Provider::get)
        .collect(Collections.toList());

手动编译

手动编译:

# 模块:sample.a@1.1
# 依赖模块:无
# 源码位置:src/a/java/
# 编译路径:out/sample.a
javac -d out/sample.a $(find src/a/java -name "*.java") --module-version 1.1

# 模块:sample.b@1.0
# 依赖模块:sample.a
# 依赖查找路径:out/
# 源码位置:src/b/java/
# 编译路径:out/sample.b
javac --module-path out -d out/sample.b $(find src/b/java -name "*.java") --module-version 1.0

# 运行
java --module-path out --module sample.b/my.b.Main

注意到,java 会在 module-path 里根据模块名寻找对应的子目录或 JAR 包,因此我们的输出目录名需要准确。

手动打包:

jar --create --file lib/sample.a@1.0.jar --module-version 1.1 -C out/sample.a .

jar --create --file lib/sample.b@1.0.jar --main-class my.b.Main --module-version 1.0 -C out/sample.b .

# 从模块 sample.b 开始运行
java --module-path lib --module sample.b

向后兼容

向后兼容是 Java 的一贯优势。Java 9 也需要兼容那些未模块化的库。

Java Module Hierarchy

把未模块化的 JAR 放在 CLASS_PATHMODULE_PATH 均可。

将其放在 MODULE_PATH 时,将被视为自动模块(Automatic Module)。自动模块会 exports opens 其内所有的包。

自动模块会解析 JAR 文件名来生成模块名,如果失败则会报错 java.lang.module.ResolutionException: Unable to derive module desciptor for: xxx

将其放在 CLASS_PATH 时,将被视为未命名模块(Unnamed Module)。未命名模块无法被 requires 使用。

原文链接 https://blog.xupu.name//p/2018-10-intro-to-java-modularity/

如无特别指明,本站原创文章均采用 CC BY-NC-ND 4.0 许可,转载或引用请注明出处,更多许可信息请查阅这里