ide 两个模块的jdk版本不一样_Java平台模块系统(3)- JDK工具
在完成项目模块的源代码之后,我们需要编译和运行这些模块。大部分时候,我们都是在IDE上进行开发和测试,可以把编译和运行的工作交给IDE来完成。不过我们仍然可以用javac
和java
来分别编译和运行代码。了解这些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这些构建工具,应该已经会自动记录版本号。因此,除非是直接使用javac
或jar
命令,我们不需要担心版本号的问题。需要注意的是,模块系统在查找模块时会忽略版本信息。如果一个模块路径中包含了具有相同名称,但是版本号不同的模块,模块系统仍然认为这是一个错误。在解析模块时,模块系统只考虑模块名称。
模块版本可以通过参数--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.bind
和java.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
来创建示例应用的自定义镜像时,会看到如下错误。
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.jar
的module-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工具相关推荐
- linux修改jdk版本无效,关于windows和linux系统更换JDK版本后,修改环境变量也无法生效的原因和解决办法...
今天遇到了一个问题: 我linux系统之前安装JDK12,今天将其改成了JDK1.8,并修改了环境变量,但是通过java -version命令显示的依旧是JDK12的版本. 这是因为,当使用安装版本的 ...
- java中更改jdk版本_win10更改jdk版本的方法是什么_win10系统如何更改jdk版本图文教程...
最近有朋友问小编win10更改jdk版本的方法是什么,对于win10更改jdk版本的问题,应该有很多朋友不明白.有时候我们电脑中的jdk版本不适用,需要更改jdk版本,但是有的朋友不知道一个如何修改, ...
- java 平台级模块系统_Java平台模块系统公众审查未能通过
java 平台级模块系统 在过去的几周里,Java世界中的戏剧,阴谋和政治活动异常高涨,最终在本周的JSR 376 Java平台模块系统公共评审投票中达到了顶峰. Java模块化(包括Java平台模块 ...
- java 面试问jdk版本_面试常被问到的 JDK 命令,你知道几个?
Hello 大家好,我是鸭血粉丝,不知道你有没有过这样的经历,经常在面试的时候被问到 JDK 相关的命令,如何排查线上的问题,线上程序突然崩了要怎么处理,等等类似这种场景.其实并不是每个开发人员都能有 ...
- 9.设置jdk版本相同有哪些需要设置的(解决jdk不同的一般方法)
1.项目右键----->build path------>configura build path----->设置jdk 2. windows--- proferences--- j ...
- idea怎么看jdk版本_怎么看自己的jdk版本
这个主要是在cmd下输入java-version来查看,64位的效果如下:如果没有标明是多少位的,默认一般是32位的,希望对你有用,我是ndk吧吧主,有问题可以ndk吧留言,谢谢! . 就安装最新版就 ...
- 电脑新安装JDK版本并运行使用该JDK版本问题
情景:电脑上已正常安装一个jdk版本,如:1.7.0_71,因考虑到一些情况,现需要使用版本为1.7.0_80(1.8),故需新安装JDK,并使服务可以运行使用新安装的JDK版本. 网络找寻方法: ( ...
- 升级JDK版本注意事项
@Author Frank @Date 2019-07-26 说明 JDK版本升级是Java开发人员一定会遇到的事情,为了保证生产环境的稳定,JDK升级需要多方面考虑,笔者从自身主导的多个系统的JDK ...
- okhttp与jdk版本不兼容分析
1.背景 最近在部署应用的时候,代码几乎没有太大改动.结果报了如下错误[clientBuilder.sslSocketFactory(SSLSocketFactory) not supported o ...
最新文章
- 原生ajax XMLHTTPRequest()
- html5 canvas图片渐变
- C++ 高级篇(一)—— 模板(Templates)
- python难度如何_【经验分享】想转行学python,过来人提醒大家几点
- ThinkPHP利用数据库字段做栏目的无限分类
- 完成端口的回射服务器,给别人的代码添加了补丁(竞争问题,发送数据)
- Swift iOS : 代码分析DrawController
- 田彩蝶(帮别人名字作诗)
- 工业树莓派在激光雕刻中的应用
- vbs代码未结束的字符串常量
- 轻轻松松背单词软件测试,十款背单词软件测评报告(转载)
- jzoj 4246【五校联考6day2】san
- 回收站删除的文件怎么恢复?
- 笔记本计算机的功率一般多少钱,笔记本功率一般是多少瓦
- winform图片标尺控件
- 大数据中技术的定义和特点
- 基于WPF的桌面宠物开发(一) :WPF简介+环境搭建+简单界面
- 如何做好技术布道——用影响影响影响
- 《操作系统之哲学原理(第2版)》——— 操作系统的发展历史
- 浙江国税开票软件下载及安装打印调试指南