在完成项目模块的源代码之后,我们需要编译和运行这些模块。大部分时候,我们都是在IDE上进行开发和测试,可以把编译和运行的工作交给IDE来完成。不过我们仍然可以用javacjava来分别编译和运行代码。了解这些JDK工具的细节,可以帮助我们更好的了解模块的生命周期的细节。由于Java 9刚发布不久,IDE和构建工具等完善对其支持尚需时日。这个过程可能比较缓慢,因此在某些时候,我们还必须使用JDK内置的工具来完成某些任务。在迁移到Java 9模块系统时,可能会遇到各种相关问题。了解这些工具,可以帮助更好的分析和解决问题。

我们接下来讨论JDK中的几个内置工具。有些工具在Java 9中增加了与模块相关的参数,而有的工具是Java 9中新增的。这些工具支持一些通用的命令行参数,与模块系统的一些通用概念相对应。这些工具可以在不同的阶段中使用。

  • 编译时:使用javac命令来把Java源代码编译成字节代码。
  • 链接时:这是Java 9中引入的新的可选阶段。使用jlink命令来组装和优化一组模块及其传递依赖,并创建自定义的运行时镜像。
  • 运行时:通过java命令来启动JVM并执行字节代码。

下面首先介绍几个相关的概念。

模块路径

模块系统使用模块路径来查找在不同的模块工件(module artifact)中定义的模块。模块路径本身是一个路径的序列,其中的路径可以是模块定义的路径,也可以是包含模块定义的目录的路径。模块定义可以是模块工件,也可以是包含了模块内容的目录。模块路径中包含的路径会按照在序列中的顺序被依次搜索,直到找到第一个定义了某个特定模块的工件。模块系统使用模块路径来解析依赖关系。如果模块系统无法找到定义某个模块的工件,或者在一个目录中找到了两个定义了名称相同的模块的工件,模块系统会报错然后退出。模块路径使用操作系统上路径分隔符来进行分隔:Windows上是分号(;),macOS和Linux上是冒号(:)。

在不同的阶段可以使用不同类型的模块路径,如下表所示。正如表中所说明的那样,模块路径的不同命令行选项可以应用在多个阶段中。每个模块路径的顺序,定义了当多个模块路径存在时的查找顺序。比如,在使用javac的编译时刻,表中的4个模块路径都可以适用。模块系统会首先检查--module-source-path指定的模块路径,其次--upgrade-module-path,接着是--system,最后是--module-path-p

模块路径

所有Java内置模块和模块路径中定义的模块的集合,被称为可见模块的集合(observable modules)。可见模块的概念在模块解析的过程中非常重要。如果需要解析的模块不出现在可见模块的集合中,模块系统会报错并退出。

模块版本

尽管在模块声明中没有与版本号相关的配置,我们仍然可以记录一个模块的版本信息。推荐的做法是使用语义化版本号(semver)来记录模块版本。Maven和Gradle这些构建工具,应该已经会自动记录版本号。因此,除非是直接使用javacjar命令,我们不需要担心版本号的问题。需要注意的是,模块系统在查找模块时会忽略版本信息。如果一个模块路径中包含了具有相同名称,但是版本号不同的模块,模块系统仍然认为这是一个错误。在解析模块时,模块系统只考虑模块名称。

模块版本可以通过参数--module-version来指定。

主模块

主模块可以通过参数--module-m来指定。在运行时,主模块包含了需要运行的入口Java类。如果入口类已经被记录在模块的描述文件中,那么只需要指定模块名称就足够了。否则的话,需要通过<module>/<mainclass>的方式来指定模块名称和入口类名称。

在编译时,--module-m用来指定要编译的模块。

根模块

可见模块的集合中包含了所有可能被解析的模块。然后不是所有的可见模块都在运行时是必须的。模块系统从根模块的集合中开始解析过程,通过传递依赖关系不断地把新的模块加入进来,得到最终的模块依赖图。有可能并不需要解析所有的可见模块,但是只有可见模块是可以被解析的。

模块系统使用一些规则来选择默认的根模块。当编译或运行未命名模块中的代码时,即Java 9之前的代码时,未命名模块对应的根模块包含JDK系统模块和应用模块。如果存在模块java.se,那么它是唯一的JDK系统模块。否则的话,所有无条件导出了至少一个包的java.*模块,都是系统模块。所有无条件导出了至少一个包的非java.*模块,都是根模块。

当编译或运行Java 9代码时,默认的根模块集合取决于阶段。

  • 在编译时,是需要编译的模块集合
  • 在链接时,是空的。
  • 在运行时,是应用的主模块。

我们可以通过参数--add-modules来添加额外的模块到跟模块集合中。该参数的值是一个逗号分隔的模块名称列表。除了模块列表之外,该参数还可以接受3个特殊值。

  • ALL-DEFAULT:添加未命名模块对应的根模块集合,参见上面所述的定义。
  • ALL-SYSTEM:添加所有的系统模块。
  • ALL-MODULE-PATH:添加模块路径中搜索到的全部可见模块。

限制可见模块

我们可以通过参数--limit-modules来限制可见模块。在应用了此参数之后,可见模块的集合被限制为一个新的模块集合的传递闭包。这个新的模块集合中包含--limit-modules指定的模块、主模块和通过--add-modules添加的模块。--limit-modules参数的值也是一个逗号分隔的模块名称列表。

升级模块路径

命令行参数--upgrade-module-path用来指定升级模块路径。该路径包含可以用来升级环境内置模块的模块。升级模块路径替代了Java已有的扩展机制。

一个系统模块是否可以被升级,已经在module-info.java里面清楚的说明。比如,模块java.xml.bindjava.xml.ws都是可以升级的。

提高可读性和打破封装

模块系统的首要目的是为了封装。然后在有些时候,我们必须要打破封装来处理遗留代码或是运行测试。我们可以下面几个命令行参数来打破封装。

  • --add-reads module=target-module(,target-module)*:更新源模块来读取目标模块。目标模块可以是ALL-UNNAMED来读取所有未命名模块。
  • --add-exports module/package=target-module(,target-module)*:更新源模块来导出包到目标模块。这会添加一个从源模块来目标模块的受限导出。目标模块可以是ALL-UNNAMED来导出到所有未命名模块。
  • --add-opens module/package=target-module(,target-module)*:更新源模块来开放包到目标模块。这回添加一个从源模块到目标模块的受限开放。
  • --patch-module module=file(;file)*:使用JAR文件或目录中的类和资源文件来覆盖或增加一个模块的内容。在需要临时修改一个模块的内容以方便测试时,--patch-module非常实用。

在介绍了基本的概念之后,我们下面来介绍JDK的工具。

javac

javac命令支持下列与模块相关的参数。这些参数的含义已经在之前的小节中进行了介绍。

  • --module-m
  • --module-path-p
  • --module-source-path
  • --upgrade-module-path
  • --system
  • --module-version
  • --add-modules
  • --limit-modules
  • --add-exports
  • --add-reads
  • --patch-module

我们通过在介绍传递依赖一节中使用的模块来说明javac的用法。我们在一个目录中保存了所有这些模块的源代码。每个模块有其自己的子目录。我们可以使用如下命令来编译单个模块。模块C没有任何其他依赖,可以被直接编译。

$ javac -d ~/Downloads/modules_output/C C/**/*.java

为了编译模块B,我们使用-p来指定编译好的模块C,因为模块B依赖模块C。当模块依赖第三方库时,也需要使用-p来指定路径。

$ javac -d ~/Downloads/modules_output/B -p ~/Downloads/modules_output B/**/*.java

我们有全部模块的源代码,可以直接编译全部模块。

$ javac -d ~/Downloads/modules_output --module-source-path . **/*.java

我们也可以使用-m来编译单个模块。当使用-m时,需要使用--module-source-path来指定模块的源代码路径。

$ javac -d ~/Downloads/modules_output --module-source-path . -m B

jlink

运行Java程序需要在本机上安装JRE或JDK。如前所述,在Java 9之前,并没有简单有效的方式来对JRE或JDK的内容进行定制。即便是一个最简单的“Hello World”程序也需要完整大小的JRE来运行。JDK的模块化使得创建自定义的Java运行时镜像变得可能。自定义的镜像只包含应用程序运行所需的模块,从而可以极大地降低镜像的大小。

我们使用jlink工具来创建自定义镜像。假设我们有一个模块demo.simple,其中的Java类test.Main用来输出“Hello World”。这是一个最简单的Java应用程序。我们使用jar工具创建了该模块的JAR文件demo.simple-1.0.0.jar,并记录了入口Java类。下面的代码给出了使用jlink创建自定义镜像的示例。路径<module_dir>包含了模块demo.simple的工件。路径<JDK_PATH>/jmods则包含了JDK模块。

$ jlink -p <module_dir>:<JDK_PATH>/jmods--add-modules demo.simple --output <output_dir> --launcher simple=demo.simple

jlink工具会在指定的输出目录中创建自定义镜像。我们可以运行镜像目录下的子目录bin中的可执行文件simple来运行该应用程序。在macOS上,创建出来的自定义镜像的大小只有36.5MB,远远低于完整的JRE的大小。下图中给出了自定义镜像中的内容。

自定义镜像中的内容

jlink工具也支持很多不同的命令行参数,如下表所示。

jlink的参数

不过jlink工具有一个很大的局限性。那就是jlink不支持自动模块,这也意味着没有升级到模块的第三方库无法使用jlink来链接。举例来说,当尝试使用jlink来创建示例应用的自定义镜像时,会看到如下错误。

Error: module-info.class not found for slf4j.api module

从错误消息中我们可以得知,jlink需要模块slf4j.api中包含module-info.class文件。但是SLF4J还没有升级为Java 9模块,因此无法包括在镜像中。

java

java命令支持下列与模块相关的新参数。

  • --module-path-p
  • --upgrade-module-path
  • --add-modules
  • --limit-modules
  • --list-modules:列出所有的可见模块。
  • --describe-module-d:描述模块。
  • --validate-modules:校验所有模块。可以用来发现冲突和错误。
  • --show-module-resolution:在JVM启动时输出模块解析结果。
  • --add-exports
  • --add-reads
  • --add-opens
  • --patch-module

对于示例应用来说,在使用Maven的assembly插件把所有的第三方库和模块拷贝到一个目录之后,我们可以用下面的命令来运行应用程序。

$ java -p <path> -m io.vividcode.store.runtime/io.vividcode.store.runtime.Main

如果我们在模块工件中记录了主Java类,可以直接使用java -p <path> -m io.vividcode.store.runtime来运行应用。

选择--list-modules在调试与模块解析相关的问题时非常有用,因为它可以列出来所有的可见模块。比如,我们可以使用java --list-modules来列出来所有的JDK系统模块。当我们使用-p来添加模块路径时,输出中会包含在模块路径中查找到的模块。选项--show-module-resolution在解决模块解析问题时也非常实用。

jdeps

jdeps工具用来分析模块的依赖关系。下面的命令输出模块io.vividcode.store.runtime的描述信息,以及分析出来的模块依赖关系和压缩了传递依赖之后的依赖关系图。

$ jdeps --module-path <path> --check io.vividcode.store.runtime

上述jdeps命令的输出结果如下所示。

io.vividcode.store.runtime (file:///<path>/runtime-1.0.0-SNAPSHOT.jar)[Module descriptor]requires io.vividcode.store.filestore;requires io.vividcode.store.product.persistence;requires mandated java.base (@9-ea);requires slf4j.simple;[Suggested module descriptor for io.vividcode.store.runtime]requires io.vividcode.store.common;requires io.vividcode.store.product;requires io.vividcode.store.product.persistence;requires mandated java.base;[Transitive reduced graph for io.vividcode.store.runtime]requires io.vividcode.store.product.persistence;requires mandated java.base;

使用--check参数要求事先知道模块名称。如果只有一个JAR文件,可以使用参数--list-deps--list-reduced-deps

$ jdeps --module-path <path> --list-deps <path>/runtime-1.0.0-SNAPSHOT.jar

上述命令的输出结果如下所示。

io.vividcode.store.common
io.vividcode.store.product
io.vividcode.store.product.persistence
java.base

参数--list-deps--list-reduced-deps的区别在于,--list-reduced-deps参数的结果中不包含模块依赖关系图中的隐式读取边。

$ jdeps --module-path <path> --list-reduced-deps <path>/runtime-1.0.0-SNAPSHOT.jar

上述命令的输出如下所示。

io.vividcode.store.product.persistence

jdeps工具的另外一个功能是生成graphviz的DOT文件,可以通过图的方式来可视化模块依赖关系。下面的命令生成模块io.vividcode.store.runtime的依赖关系DOT文件。

$ jdeps --module-path <module_path>  --dot-output <dot_output_path> -m io.vividcode.store.runtime

在输出目录中包含了一个对应于全部模块的DOT文件summary.dot,以及一个对应于模块的文件io.vividcode.store.runtime.dot。接着我们可以把DOT文件转化成PNG文件来查看。需要首先安装graphviz。

$ dot -Tpng <dot_output_path>/summary.dot  -o <dot_output_path>/summary.png

我们可以使用jdeps来处理多个JAR文件来生成整个项目的模块依赖关系图。生成的summary.dot文件中包含了全部模块。对于示例应用,我们使用Maven来把所有的模块工件和第三方库复制到不同的目录。可以使用Maven的dependency插件来完成。我们接着可以使用如下命令来生成DOT文件。<third_party-libs-path>是第三方库所在的路径,<modules_path>是所有模块工件的路径。

$jdeps --module-path <third_party-libs-path> --dot-output <dot_output_path> <modules_path>/*.jar

接着我们把生成的summary.dot文件转换成PNG文件,如下图所示。

模块依赖关系图

jdeps可以用来从JAR文件中生成模块声明文件module-info.java。选项--generate-module-info--generate-open-module可以分别用来生成正常模块和开放模块的声明。比如,我们可以用下面的命令来生成JAR文件jackson-core-2.8.7.jarmodule-info.java文件。

生成的module-info.java文件如下所示。该声明文件只是简单地导出了全部包和添加了服务提供者。生成的module-info.java文件可以作为把遗留JAR文件迁移到Java 9的基础。

module jackson.core {exports com.fasterxml.jackson.core;exports com.fasterxml.jackson.core.base;exports com.fasterxml.jackson.core.filter;exports com.fasterxml.jackson.core.format;exports com.fasterxml.jackson.core.io;exports com.fasterxml.jackson.core.json;exports com.fasterxml.jackson.core.sym;exports com.fasterxml.jackson.core.type;exports com.fasterxml.jackson.core.util;provides com.fasterxml.jackson.core.JsonFactory withcom.fasterxml.jackson.core.JsonFactory;}

ide 两个模块的jdk版本不一样_Java平台模块系统(3)- JDK工具相关推荐

  1. linux修改jdk版本无效,关于windows和linux系统更换JDK版本后,修改环境变量也无法生效的原因和解决办法...

    今天遇到了一个问题: 我linux系统之前安装JDK12,今天将其改成了JDK1.8,并修改了环境变量,但是通过java -version命令显示的依旧是JDK12的版本. 这是因为,当使用安装版本的 ...

  2. java中更改jdk版本_win10更改jdk版本的方法是什么_win10系统如何更改jdk版本图文教程...

    最近有朋友问小编win10更改jdk版本的方法是什么,对于win10更改jdk版本的问题,应该有很多朋友不明白.有时候我们电脑中的jdk版本不适用,需要更改jdk版本,但是有的朋友不知道一个如何修改, ...

  3. java 平台级模块系统_Java平台模块系统公众审查未能通过

    java 平台级模块系统 在过去的几周里,Java世界中的戏剧,阴谋和政治活动异常高涨,最终在本周的JSR 376 Java平台模块系统公共评审投票中达到了顶峰. Java模块化(包括Java平台模块 ...

  4. java 面试问jdk版本_面试常被问到的 JDK 命令,你知道几个?

    Hello 大家好,我是鸭血粉丝,不知道你有没有过这样的经历,经常在面试的时候被问到 JDK 相关的命令,如何排查线上的问题,线上程序突然崩了要怎么处理,等等类似这种场景.其实并不是每个开发人员都能有 ...

  5. 9.设置jdk版本相同有哪些需要设置的(解决jdk不同的一般方法)

    1.项目右键----->build path------>configura build path----->设置jdk 2. windows--- proferences--- j ...

  6. idea怎么看jdk版本_怎么看自己的jdk版本

    这个主要是在cmd下输入java-version来查看,64位的效果如下:如果没有标明是多少位的,默认一般是32位的,希望对你有用,我是ndk吧吧主,有问题可以ndk吧留言,谢谢! . 就安装最新版就 ...

  7. 电脑新安装JDK版本并运行使用该JDK版本问题

    情景:电脑上已正常安装一个jdk版本,如:1.7.0_71,因考虑到一些情况,现需要使用版本为1.7.0_80(1.8),故需新安装JDK,并使服务可以运行使用新安装的JDK版本. 网络找寻方法: ( ...

  8. 升级JDK版本注意事项

    @Author Frank @Date 2019-07-26 说明 JDK版本升级是Java开发人员一定会遇到的事情,为了保证生产环境的稳定,JDK升级需要多方面考虑,笔者从自身主导的多个系统的JDK ...

  9. okhttp与jdk版本不兼容分析

    1.背景 最近在部署应用的时候,代码几乎没有太大改动.结果报了如下错误[clientBuilder.sslSocketFactory(SSLSocketFactory) not supported o ...

最新文章

  1. 原生ajax XMLHTTPRequest()
  2. html5 canvas图片渐变
  3. C++ 高级篇(一)—— 模板(Templates)
  4. python难度如何_【经验分享】想转行学python,过来人提醒大家几点
  5. ThinkPHP利用数据库字段做栏目的无限分类
  6. 完成端口的回射服务器,给别人的代码添加了补丁(竞争问题,发送数据)
  7. Swift iOS : 代码分析DrawController
  8. 田彩蝶(帮别人名字作诗)
  9. 工业树莓派在激光雕刻中的应用
  10. vbs代码未结束的字符串常量
  11. 轻轻松松背单词软件测试,十款背单词软件测评报告(转载)
  12. jzoj 4246【五校联考6day2】san
  13. 回收站删除的文件怎么恢复?
  14. 笔记本计算机的功率一般多少钱,笔记本功率一般是多少瓦
  15. winform图片标尺控件
  16. 大数据中技术的定义和特点
  17. 基于WPF的桌面宠物开发(一) :WPF简介+环境搭建+简单界面
  18. 如何做好技术布道——用影响影响影响
  19. 《操作系统之哲学原理(第2版)》——— 操作系统的发展历史
  20. 浙江国税开票软件下载及安装打印调试指南

热门文章

  1. css实现文字太长,显示省略号
  2. 函数指针与回调函数、句柄
  3. 转:Session,有没有必要使用它?
  4. 10-2-文章分页展示
  5. 超大规模集成电路先进光刻理论与应用_中科院上海光机所光刻机投影物镜热效应模型研究取得进展...
  6. eclipse启动springboot项目_多模块项目中的一个Spring Boot启动错误
  7. 狼殿下高清壁纸|不用等的好剧!
  8. 炫彩渐变海报版式海报
  9. UI设计素材|社交界面模板
  10. 帮助UI设计师缕清思路的GUI模板素材