JDK9引入了模块系统,这篇笔记简单介绍了引入module机制的初衷,module的定义、使用与打包。
Overview
没有引入module之前的JDK8项目结构如下,一个项目下的代码按照package进行组织。
引入了module之后的项目结构如下,在package之上还有一个module层次。
为什么引入module?
- Reliable configuration,在module之前,JVM通过classpath查找各个类,如果某个类缺失,只有当使用到这个类的时候才会发现,使用module可以清晰地配置各个module之间的依赖关系。
- Strong encapsulation,使用module机制可以定义一个module中那些包是对module外可见的,这就又添加了一层访问控制,增加了封装程度。
- Scalable Java platform,使用module之后,原先包含大量代码的Java SE被分割成95个module,代码更加容易维护。而且你可以只选择需要的module,例如在一些嵌入式设备上,如果不需要GUI,完全可以不把GUI模块包含在Java runtime中。
- Greater platform integrity,有些内部API是供内部使用的,不是给应用开发人员使用的,但是这些API在module机制之前能够被访问到,由于这些内部API有部分是平台相关的,使用这些API不利于各个平台统一(platform integrity),而module添加了一层访问控制,使得一些内部的API真正意义上地被隐藏了起来。
- Improved performance,性能更好了,具体为什么更好请查看JSR376
实践
下面使用如下的项目结构说明对模块的各种操作,项目分为两个模块 utils
和 test
模块,我会依次介绍模块的定义、模块的调用、模块的运行。
.
├── test
│ ├── module-info.java
│ └── org
│ └── example
│ └── test
│ └── TestStringUtils.java
└── utils
├── module-info.java
└── org
└── example
└── utils
└── StringUtils.java
8 directories, 4 files
StringUtils.java
的内容:
package org.example.utils;
public class StringUtils {
public String echo(String s) {
return s;
}
}
TestStringUtils.java
的内容:
package org.example.test;
import org.example.utils.StringUtils;
public class TestStringUtils{
public static void main(String[] args) {
StringUtils util = new StringUtils();
System.out.println(util.echo("hello"));
}
}
module的定义
和Python中的模块类似,Java也使用一个文件来标志模块。如果一个目录中有名为 module-info.java
的文件,那么这个目录就被当成一个模块,在这个目录中可以存放各个package,下面就是一个模块的结构:
utils
├── module-info.java
└── org
└── example
└── utils
└── StringUtils.java
3 directories, 2 files
module-info.java
中的内容如下:
module utils {
exports org.example.utils; // 表示暴露这个module中的这个包,这样模块外可以使用这个包
}
需要注意的是 module-info.java
中的模块名和 module-info.java
所在的目录名要一致。
如果要编译这个模块,把 module-info.java
当成普通的Java进行编译即可:
$ javac -d mod/utils utils/module-info.java utils/org/example/utils/StringUtils.java
其中 -d
参数表示把输出到 mod/utils
这个目录。
好了,现在 utils
模块编译好了,下面来看一下如何使用这个模块。
module的使用
我们在 test
模块的 module-info.java
中写入如下内容:
module test {
requires utils; // 表示这个模块需要使用到utils模块
exports org.example.test; // 暴露test这个模块中的包
}
然后像之前一样编译这个模块,不过由于 test
模块需要用到 utils
模块,我们要使用 --module-path
告诉编译器去哪里找模块:
$ javac -d mod/test test/module-info.java test/org/example/test/TestStringUtils.java --module-path mod/
运行module
代码写完了,现在执行下面的命令来运行 test
模块中的 TestStringUtils
类:
$ java --module-path mod/ --add-modules utils,test org.example.test.TestStringUtils
hello
稍微解释一下参数, --module-path
和上文一样,用来告诉编译器去哪里找module, --add-modules
后面接一个 ,
分隔的列表,表示要加载的module,最后写明main函数所在的全限定类名 org.example.test.TestStringUtils
module的打包
和package一样,module也可以打包成一个文件,module文件的扩展名是 .jmod
。如果你的JDK版本大于8,你可以在JDK的安装目录下找到一个 jmod
目录,里面存放了JDK的各个module:
> ls /usr/lib/jvm/java-15-openjdk/jmods
java.base.jmod java.instrument.jmod java.se.jmod jdk.compiler.jmod jdk.internal.le.jmod jdk.jdwp.agent.jmod jdk.naming.rmi.jmod
java.compiler.jmod java.logging.jmod java.smartcardio.jmod jdk.crypto.cryptoki.jmod jdk.internal.opt.jmod jdk.jfr.jmod jdk.net.jmod
java.datatransfer.jmod java.management.jmod java.sql.jmod jdk.crypto.ec.jmod jdk.internal.vm.ci.jmod jdk.jlink.jmod jdk.nio.mapmode.jmod
java.desktop.jmod java.management.rmi.jmod java.sql.rowset.jmod jdk.dynalink.jmod jdk.internal.vm.compiler.jmod jdk.jshell.jmod jdk.sctp.jmod
javafx.base.jmod java.naming.jmod java.transaction.xa.jmod jdk.editpad.jmod jdk.internal.vm.compiler.management.jmod jdk.jsobject.jmod jdk.security.auth.jmod
javafx.controls.jmod java.net.http.jmod java.xml.crypto.jmod jdk.hotspot.agent.jmod jdk.jartool.jmod jdk.jstatd.jmod jdk.security.jgss.jmod
javafx.fxml.jmod java.prefs.jmod java.xml.jmod jdk.httpserver.jmod jdk.javadoc.jmod jdk.localedata.jmod jdk.unsupported.desktop.jmod
javafx.graphics.jmod java.rmi.jmod jdk.accessibility.jmod jdk.incubator.foreign.jmod jdk.jcmd.jmod jdk.management.agent.jmod jdk.unsupported.jmod
javafx.media.jmod java.scripting.jmod jdk.aot.jmod jdk.incubator.jpackage.jmod jdk.jconsole.jmod jdk.management.jfr.jmod jdk.xml.dom.jmod
javafx.swing.jmod java.security.jgss.jmod jdk.attach.jmod jdk.internal.ed.jmod jdk.jdeps.jmod jdk.management.jmod jdk.zipfs.jmod
javafx.web.jmod java.security.sasl.jmod jdk.charsets.jmod jdk.internal.jvmstat.jmod jdk.jdi.jmod jdk.naming.dns.jmod
下面以 utils
这个module为例,介绍对module的打包操作,编译产生的module结构如下:
> tree mod/utils/ <<<
mod/utils/
├── module-info.class
└── org
└── example
└── utils
└── StringUtils.class
打包有两种方法,一种是先打jar包,然后打jmod包,一种是直接打jmod包。
方法一 jar -> jmod
首先进入模块目录 mod/utils
目录,然后执行下面命令:
$ jar -cf ../utils.jar .
打完的jar包会存储在 mod
目录下,然后回到 mod
目录,执行下面命令,生成 utils.jmod
包
$ jmod create --class-path utils.jar utils.jmod
方法二 直接打jmod包
在 mod
目录下直接运行下面的命令即可生成 utils.jmod
包
$ jmod create --class-path utils/ utils.jmod
打完jmod包之后,可以使用 jmod list utils.jmod
查看其中的内容。
为了方便下面的演示,也对 test
模块打个包,在 mod
目录下执行下面命令
$ jmod create --class-path test --main-class org.example.test.TestStringUtils test.jmod
其中的 --main-class
用于指明module中的main函数所在的类。
使用jlink生成一个包含制定模块的JRE环境
如果你是按照顺序执行上述命令到这,现在在 mod
下应该有 utils
, test
两个目录, utils.jmod
, test.jmod
两个文件。由于jmod是一个模块,包含 module-info.java
目录也是模块,在 mod
下就有四个模块,其中两个是重复的。为了保证下面实验的正确性,需要将两个目录或者两个文件移出 mod
目录。
在 mod
目录下执行下面命令
$ jlink --module-path . --add-modules test,utils --output /tmp/jlink
上面的命令会在 /tmp/jlink
目录下生成JRE环境,这个JRE环境已经包含了我们自定义的两个模块 utils
和 test
,执行下面的代码运行 test
模块中的 org.example.test.TestStringUtils
类
$ /tmp/jlink/bin/java -m test/org.example.test.TestStringUtils │ An options file is a text file that contains the options and values that you would ordi‐
hello