一些整理 Project Jigsaw warn: requires transitive directive for an automatic module jmod-example 迁移中遇到的一些问题 module xxx read package xxx from both xxx and xxx can’t extract module name from xxx.jar: Provider class xxx not int module Java 9 Migration Guide: The Seven Most Common Challenges 强烈推荐这一篇文章 基于下面这俩链接,基本说明了在下一个大版本,将对JPMS进行支持 Spring Boot Java 9+ modularity Declare Spring modules with […]
转自 推荐去出处看,这个系列关于JPMS讲的还不错 一. 什么是模块API 模块API由可以让你对模块进行编程访问的类和接口组成。 使用API,可以通过编程方式: 读取,修改和构建模块描述符 加载模块 读取模块的内容 搜索加载的模块 创建新的模块层 模块API很小。 它由大约15个类和接口组成,分布在两个包中: java.lang java.lang.module Module,ModuleLayer和LayerInstantiationException类在java.lang包中,其余的在java.lang.module包中。 下表包含模块API中的类的列表,每个类的简要说明。 列表未排序。 首先列出了Module和ModuleDescriptor,因为应用程序开发人员最常使用它们。 所有其他类通常由容器和类库使用。 该列表不包含Module API中的异常类。 类 描述 Module 表示运行时模块。 ModuleDescriptor 表示模块描述。 这是不可变类。 ModuleDescriptor.Builder 用于以编程方式构建模块描述的嵌套构建器类。 ModuleDescriptor.Exports 表示模块声明中的exports语句的嵌套类。 ModuleDescriptor.Opens 表示模块声明中的opens语句的嵌套类。 ModuleDescriptor.Provides 表示模块声明中的provides语句的嵌套类。 ModuleDescriptor.Requires 表示模块声明中的requires语句的嵌套类。 ModuleDescriptor.Version 表示模块版本字符串的嵌套类。 它包含一个从版本字符串返回其实例的parse(String v)工厂方法。 ModuleDescriptor.Modifier 枚举类,其常量表示在模块声明中使用的修饰符,例如打开模块的OPEN。 ModuleDescriptor.Exports.Modifier 枚举类,其常量表示在模块声明中用于exports语句的修饰符。 ModuleDescriptor.Opens.Modifier 枚举类,其常量表示在模块声明中的opens语句上使用的修饰符。 ModuleDescriptor.Requires.Modifier 枚举类,其常量表示在模块声明中的requires语句上使用的修饰符。 ModuleReference 模块的内容的引用。 它包含模块的描述及其位置。 ResolvedModule […]
转自 Maven + JPMS的相关整合可以参考这一个(看着有点像jmod + jlink): jmod-example 使用Idea可以很方便的进行整合,但是当下针对Spring Boot项目,还需要更多的资料。 主体感觉是还有很长的路要走。 在完成项目模块的源代码之后,我们需要编译和运行这些模块。大部分时候,我们都是在IDE上进行开发和测试,可以把编译和运行的工作交给IDE来完成,当下IDE已经能帮我们完成较多的迁移工作。不过我们仍然可以用javac和java来分别编译和运行代码。了解这些JDK工具的细节,可以帮助我们更好的了解模块的生命周期的细节。这里先使用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 […]
转自 Project Jigsaw warn: requires transitive directive for an automatic module 如果在Java 9上开发全新的应用,那么上面介绍的模块声明文件中的概念就已经足够了。不过在很多情况下,我们需要兼容已有的应用。这个时候就需要了解本节中介绍的未命名模块(unnamed module)和自动模块(automatic module)的概念。 未命名模块 从上面的小节中我们可以看到,Java 9对如何在不同的模块之间访问Java类型有严格的限制。如果你从零开始开发一个Java9上的新应用,那么你应该使用新的模块系统。Java 9仍然提供了良好的向后兼容性,可以运行所有在Java 9之前编写的应用程序。这是通过未命名模块来实现的。 如果模块系统需要加载一个来自不在任何模块所声明导出的包中的Java类型时,它会尝试从CLASSPATH中加载。如果该Java类型被成功加载,那么该类型被视为是一个特殊模块的成员。该特殊的模块称为未命名模块。未命名模块的特殊性在于它读取所有其他的模块,并且导出所有内部包含的包。 如果一个类型是从CLASSPATH中加载的,那么作为未命名模块中的成员,它可以访问所有其他模块中所导出的包,也包括Java平台内部的模块。正因为这样,Java 9之前编写的应用,可以不经过任何改动就运行在Java 9之上。虽然未命名模块导出了所有内部的包,但是其它命名模块中的代码并不能访问未命名模块中的类型。我们也没办法通过requires来声明对未命名模块的依赖关系。这样的限制是必须的,否则我们就失去了引入模块系统的所有好处,又重新回到了依靠CLASSPATH的老路上去了。未命名模块的主要目的是保持向后兼容性。如果一个包同时在某个命名模块和未命名模块中出现,那么在未命名模块中的包会被忽略。所以在CLASSPATH中的包不会干扰在命名模块中的代码。 自动模块 由于Java 9是向后兼容的,对于已有的应用来说,并不一定要升级到使用模块。不过还是建议升级来利用模块系统的好处。 推荐的升级方式是采用自底向下的做法,也就是说从依赖关系树中的叶子节点开始做起。举例来说,如果一个应用有3个子模块或子项目,A、B和C。它们之间的依赖关系是A -> B -> C。当升级该应用到Java 9时,推荐的做法是从C开始,然后再依次是B和A。这样的话,当C被升级为模块之后,A和B都还在未命名模块中,可以继续访问模块C中的类型。这是因为我们之前提到的,未命名模块可以读取所有的其它命名模块。然后我们升级B为一个命名模块,并声明其依赖模块C。最后再把A升级为模块,就完成了该项目的升级。 然而这种自底向上的升级方法并不总是可行的。有些库可能是第三方所维护的,我们没有办法控制这些库升级到模块的时间。我们仍然希望可以升级那些依赖这些第三方库的代码。我们没有办法直接把应用本身的代码升级为模块,而把第三方库放在CLASSPATH中。这样的话,这些第三方库会出现在未命名模块中。而我们之前已经说过,命名模块是不能访问未命名模块中的Java类型的。为了解决这个问题,Java模块系统中有另外一个自动模块的概念。我们只需要把这些第三方库放在模块路径中,它们会被转换成自动模块。 与其他显式创建的命名模块不同的是,自动模块是从普通的JAR文件中自动创建出来的。这些JAR文件中并没有包含模块声明文件module-info.class。自动模块的名称来自于JAR文件的清单文件MANIFEST.MF中的属性Automatic-Module-Name,或从JAR文件的名称中自动推断出来的。其他模块可以使用该名称来声明对该自动模块的依赖。推荐的做法是使用清单文件的属性Automatic-Module-Name,比依赖JAR文件名称的做法要更加可靠。 自动模块的特殊性体现在下面几个方面: 自动模块读取所有其它的命名模块。 自动模块导出所包含的全部包。 自动模块读取未命名模块。 自动模块对其它自动模块是传递可读的。 自动模块是CLASSPATH和命名模块之间的桥梁。最终的目的当然是把Java 9之前的那些子模块、子项目和库都升级到Java 9的命名模块。但是在升级过程中,我们可以把这些子模块、子项目和库的JAR文件加入到模块路径中作为自动模块来使用。 在下面的代码中,模块D中的类dtest.MyD使用了Google Guava库中的com.google.common.collect.Lists。 package dtest; import com.google.common.collect.Lists; public class MyD { […]
转自 这一个系列也很不错:JPMS 在提到Java 9时,最重要的话题是Project Jigsaw,也就是Java平台模块系统(Java Platform Module System,JPMS)。JPMS把模块化引入了Java平台中。Project Jigsaw本来计划作为Java 8的一部分,但是由于所涉及的改动过大,因此推迟到了Java 9中。模块系统不仅给Java平台本身带来了巨大的改动,也给在Java平台上运行的应用程序带来了革命性的变化。 在Java 9中,Java SE平台和JDK本身都以模块化的方式来组织,因此在经过缩减之后可以运行在小型设备上。在Java 9 之前,JDK和JRE的安装是一个不开切分的整体。JRE中包含了不同的应用程序所需要使用的各种工具和标准库。但对于一个特定的应用来说,在绝大多数情况下都只会用到其中一部分的工具和标准库。这就意味着JRE中的部分内容其实是完全多余的。举例来说,一个API代理程序很可能永远都用不到与用户界面相关的AWT/Swing库。在完成Java平台本身的模块化之后,开发人员就可以通过移除不必要的模块的方式来为应用打造专属的JRE,而只保留该应用所需要的模块。这可以极大的减少应用程序安装包的大小,从而节省存储空间和网络传输带宽。 Java社区一直以来都希望有一种方式来构建模块化Java应用。OSGi是目前比较流行的一个选择。Project Jigsaw同样可以让开发人员构建模块化的Java库和应用。相较于OSGi而言,Java平台自身提供的模块化实现显然更有吸引力。 Project Jigsaw本身是一个非常复杂的实现,其基本内容定义在JSR 376: JavaTM Platform Module System中,还有与之对应的6个JEP。 200:模块化JDK 201:模块化源代码 220:模块化运行时镜像 260:封装大部分内部API 261:模块系统 282:jlink – Java链接器 本章中包含与JPMS相关的重要内容。 模块概述 根据Oracle的Java平台集团的首席架构师Mark Reinhold在一篇文章中的论述: 模块是一个命名的,自我描述的代码和数据的集合。模块的代码被组织成多个包,每个包中包含Java类和接口;模块的数据则包括资源文件和其他静态信息。 从上述对模块的定义可以知道,模块只是按照预先定义的结构来进行组织的编译后的Java代码。如果你已经使用Maven的多模块功能,或是Gradle的多项目功能,那么每个Maven模块或Gradle项目都可以很容易地转换成JPMS模块。 每个模块都需要有一个名字。模块应该遵循与Java包同样的命名惯例,也就是翻转域名模式,如com.mycompany.mymodule。 一个JPMS模块通过根目录中的module-info.java文件来描述。该文件被编译成module-info.class。在这个文件中,我们使用新的关键词module来声明一个模块。下面的代码中给出了模块com.mycompany.mymodule所对应的module-info.java文件的内容。该文件只是声明了一个模块,并没有对它进行具体的描述。相关的内容会在后续的小节中提到。 module com.mycompany.mymodule { […]