当前教程
命令行构建模块
系列中的下一篇

系列中的上一篇: 使用自动模块进行增量模块化

系列中的下一篇: 强封装(JDK 内部)

命令行构建模块

当使用模块系统为您的代码创建模块时,您可能在一个使用构建工具的项目中这样做,因此构建工具的任务是确保一切正确。但是,了解“正确”是什么以及如何正确配置javacjarjava来编译、打包和运行您的应用程序将非常有帮助。这将使您更好地理解模块系统,并在构建工具无法正确处理时帮助调试问题。

注意:您需要了解模块系统基础知识才能充分利用本文。您可能还想查看核心 JDK 工具的描述

基本构建

给定一个包含一些源文件、模块声明和一些依赖项的项目,以下是如何以最简单的方式编译、打包和运行它

# compile sources files, including module-info.java
$ javac
    --module-path $DEPS
    -d $CLASS_FOLDER
    $SOURCES
# package class files, including module-info.class
$ jar --create
    --file $JAR
    $CLASSES
# run by specifying a module by name
$ java
    --module-path $JAR:$DEPS
    --module $MODULE_NAME/$MAIN_CLASS

其中包含许多占位符

  • $DEPS是依赖项列表。这些通常是通过:(Unix)或;(Windows)分隔的 JAR 文件路径,但在模块路径上,这也可以只是文件夹名称(没有在类路径上所需的/*技巧)。
  • $CLASS_FOLDER是将写入*.class文件的文件夹的路径。
  • $SOURCES*.java文件列表,必须包括module-info.java
  • $JAR是将创建的 JAR 文件的路径。
  • $CLASSES是编译期间创建的*.class文件列表(因此位于$CLASS_FOLDER中),必须包括module-info.class
  • $MODULE_NAME/$MAIN_CLASS是初始模块的名称(即模块解析开始的模块),后跟包含应用程序main方法的类的名称。

对于具有常见src/main/java结构的简单“Hello World”风格项目,只有一个源文件,依赖项位于deps文件夹中,并使用 Maven 的target文件夹,它将如下所示

$ javac
    --module-path deps
    -d target/classes
    src/main/java/module-info.java
    src/main/java/com/example/Main.java
$ jar --create
    --file target/hello-modules.jar
    target/classes/module-info.class
    target/classes/com/example/Main.class
$ java
    --module-path target/hello-modules.jar:deps
    --module com.example/com.example.Main

定义主类

jar选项--main-class $MAIN_CLASS$MAIN_CLASS嵌入到模块描述符中,作为包含main方法的类,这使您可以启动模块,而无需命名主类

$ jar --create
    --file target/hello-modules.jar
    --main-class com.example.Main
    target/classes/module-info.class
    target/classes/com/example/Main.class
$ java
    --module-path target/hello-modules.jar:deps
    --module com.example

请注意,可以通过像以前一样命名它来覆盖该类并启动另一个类

# create a JAR with `Main` and `Side`,
# making `Main` the main class
$ jar --create
    --file target/hello-modules.jar
    --main-class com.example.Main
    target/classes/module-info.class
    target/classes/com/example/Main.class
    target/classes/com/example/Side.class
# override the main class and launch `Side`
$ java
    --module-path target/hello-modules.jar:deps
    --module com.example/com.example.Side

规避强封装

模块系统对内部 API 的访问非常严格:如果包未导出或打开,则将拒绝访问。但是,包不能仅仅由模块的作者导出或打开 - 还有命令行标志--add-exports--add-opens,允许模块的用户也这样做。

例如,请参见以下代码,该代码尝试创建内部类sun.util.BuddhistCalendar的实例

BuddhistCalendar calendar = new BuddhistCalendar();

要编译和运行它,我们需要使用--add-exports

javac
    --add-exports java.base/sun.util=com.example.internal
    module-info.java Internal.java
# package with `jar`
java
    --add-exports java.base/sun.util=com.example.internal
    --module-path com.example.internal.jar
    --module com.example.internal

如果访问是反射性的...

Class.forName("sun.util.BuddhistCalendar").getConstructor().newInstance();

... 编译将无需进一步配置即可完成,但我们需要在运行代码时添加--add-opens

java
    --add-opens java.base/sun.util=com.example.internal
    --module-path com.example.internal.jar
    --module com.example.internal

有关强封装使用add-exportsadd-opens规避它的详细信息。

扩展模块图

从一组初始的根模块开始,模块系统计算所有这些模块的依赖项并构建一个图,其中模块是节点,它们的读写关系是定向边。这个模块图可以使用命令行标志--add-modules--add-reads进行扩展,这些标志分别添加模块(及其依赖项)和读写边。

例如,假设一个项目对java.sql具有可选依赖项,但该模块在其他情况下不需要。这意味着如果没有一点帮助,它不会添加到模块图中

# launch without java.sql
$ java
    --module-path example.jar:deps
    --module com.example/com.example.Main

# launch with java.sql
$ java
    --module-path example.jar:deps
    --add-modules java.sql
    --module com.example/com.example.Main

可选依赖项的另一种方法是不列出依赖项,而只使用--add-modules--add-reads添加它(这很少有用,通常不推荐 - 只是一个例子)

$ java
    --module-path example.jar:deps
    --add-modules java.sql
    --add-reads com.example=java.sql
    --module com.example/com.example.Main

有关使用--add-modules--add-reads扩展模块图的详细信息。


上次更新: 2021 年 9 月 14 日


当前教程
命令行构建模块
系列中的下一篇

系列中的上一篇: 使用自动模块进行增量模块化

系列中的下一篇: 强封装(JDK 内部)