0.导引
Java发展的越来越无所不能了,从Java 9+开始而引入的平台模块系统,就是一个很大的新特性,使组织代码变得更容易,也更易于功能解耦化和微型化。本文是一个使用Java模块的简短快速上手指南。
在Java 9之前,Java的顶级代码组织元素一直是包。从Java 9开始变了:在包package的上面是模块module。模块将相关的包收集聚合在一起。
Java平台模块系统(JPMS,Java Platform Module System)是一种代码级结构,其不会改变将Java打包到JAR文件中的事实。本质上讲,所有内容仍然打包在JAR文件中。模块系统通过结合 module-info.java文件,添加了jar可以使用的新的高级描述符。
大型应用程序和组织机构,可以利用模块化来更好地组织代码。其实现在每个人都在使用着模块,因为JDK及其类现在已经模块化了。
1.为什么Java需要模块
JPMS是Jigsaw工程的成果,该工程的目标如下:
- l 使开发人员更容易组织大型应用程序和库;
- l 改进平台和JDK本身的结构和安全性;
- l 提高应用程序性能;
- l 对于较小的设备,更好地处理平台的分解面。
值得注意的是,JPMS是一个SE(标准版)特性,因此会从头影响Java的各个方面。尽管如此,这种结构改变后的设计,当在从Java 8迁移到Java 9时,最大程度的允许功能代码无需修改即可运行。这里有一些例外,我们将在稍后的概述中进行说明。
模块背后的主要思想是允许相关包的集合对模块是可见的,而对模块的外部使用者隐藏一些要素。换句话说,模块是允许另一种级别的封装。
2.类路径和模块路径
到目前为止,在Java中,类路径一直是运行着的程序的可用内容的底线。尽管类路径服务于此目的,并且很容易理解,但它最终将是一个大的、无差异的桶,所有依赖项都被放置在其中。
模块路径在类路径之上添加了一个级别。它充当包的容器,并决定哪些包对应用程序可用。
3.JDK中的模块
JDK本身现在是由模块组成的。让我们从JPMS的细节开始。
如果您的系统上有JDK,那么您也有源代码。如果您不熟悉JDK以及如何获得它,请参阅@牛旦IT课堂的这篇文章:JDK 、JRE 与JVM之间的关系及区别 。
在JDK安装目录中有一个/lib目录。在该目录中有一个src.zip文件。将其解压缩到/src目录中。
查看/src目录,并导航到/java.base目录。在那里您将找到module-info.java文件,然后打开它。
在头部的Javadoc注释之后,您会发现一个命名为module java.base的部分,其随后有一系列exports行。这里我们不详细讨论格式,因为它相当深奥。详情请点击这里的连接(https://blog.joda.org/2017/04/java-se-9-jpms-module-naming.html)——有机会我再详解之。
您可以看到许多熟悉的Java包,比如java.io,是从java.base模块导出的。这是集合或汇聚相关包的模块本质。
exports的另一面是requires指令。这允许被定义的模块被引入(需要)到某个模块。
在对模块运行Java编译器时,可以类似于类路径的方式指定模块路径。这样就可以解决依赖问题。
4.创建模块化Java项目
让我们来看看模块化Java项目是如何构建的。我打算创建一个有两个模块的小程序,一个提供依赖项,另一个使用依赖项并导出一个可执行的主类。
在文件系统中某地方创建一个新目录(模块名),叫它/com.inewday.mod1。按照约定,Java模块位于与模块同名的目录中。
现在,在这个目录中,创建一个module-info.java文件。在其中添加清单1中的内容。
清单 1: com.inewday.mod1/module-info.java
module com.inewday.mod1 {
exports com.inewday.package1;
}
请注意,模块和它导出的包是不同的名称。此处我定义了导出包的模块。
现在在这个路径上创建一个文件,在目录中包含module-info.java文件:/com.inewday.mod1/com/inewday/package1。将文件命名为Name.java。将清单2的内容写入其中。
清单2 Name.java
package com.inewday.package1;
public class Name {
public String getIt() {
return "Java Module World";
}
}
清单2将成为我们所依赖的类、包和模块。
现在我来创建另一个与/com.inewday.mod1平行的目录,并叫做/com.inewday.mod2。在这个目录中,我来创建一个module-info.java模块定义,它导入我已经创建的模块,如清单3所示。
清单 3: com.inewday.mod2/module-info.java
module com.inewday.mod2 {
requires com.javaworld.mod1;
}
清单3不言而喻,它定义了com.inewday.mod2模块,并引入/需要com.inewday.mod1。
在/com.inewday.mod2目录下,创建类路径如下:/com.inewday.mod2/com/inewday/package2。
现在在里面添加一个名为Hello.java的文件。使用清单4中提供的代码。
清单 4 Hello.java
package com.inewday.package2;
import com.inewday.package1.Name;
public class Hello {
public static void main(String[] args) {
Name name = new Name();
System.out.println("Hello " + name.getIt());
}
}
在清单4中,我们从定义包开始,然后导入com.inewday.package1.Name类。请注意,这些元素一如既往地发挥作用。这些模块已经改变了包在文件结构层(而不是代码层)可用的方式。
类似地,您应该对代码本身很熟悉。它只是简单地创建一个类并对其调用一个方法来创建一个经典的“hello world”雷同示例。
5.运行模块化Java示例
第1步:创建接收编译器输出的目录。在项目的根目录中创建一个名为/target的目录。在其内部,分别为每个模块创建一个目录:/target/com.inewday.mod1和/target/com.inewday.mod2。
第2步:编译依赖项模块,并将其输出到/target目录。在项目的根目录中,输入清单5中的命令。(假设已经安装了JDK。)
清单5:构建模块1
javac -d target/com.inewday.mod1 com.inewday.mod1/module-info.java com.inewday.mod1/com/inewday/package1/Name.java
这将导致源代码随其模块信息一起被构建。
第3步:生成依赖模块。输入清单6中所示的命令。
清单6:构建模块2
javac --module-path target -d target/com.inewday.mod2 com.inewday.mod2/module-info.java com.inewday.mod2/com/inewday/package2/Hello.java
请详细看看清单6。它将module-path参数引入到javac。这允许我们以类似于--class-path开关的方式定义模块路径。在本例中,我将传入target目录,因为清单5在该目录中输出模块1。
接下来,清单6(通过-d开关)定义了模块2的输出目录。最后给出了编译的实际主题,即模块2中包含的module-info.java文件和类。
为了运行程序,使用清单7中所示的命令。
清单7:执行模块主类
java --module-path target -m com.inewday.mod2/com.inewday.package2.Hello
--module-path开关告诉Java使用/target目录作为模块根目录,也就是说,在哪里搜索模块。-m开关是告诉Java主类在什么地方。注意,我们在完全限定类名的前面加上它的模块。
您将看到输出Hello Java Module World。
6.向后兼容性
您可能很想知道如何在后Java 9世界中运行用模块化前编写的Java程序,因为以前的代码库对模块路径一无所知。答案是Java 9被设计为向后兼容,即以前代码依然可以运行在9及以后的版本中。但是,新的模块系统是一个巨大的变化,您可能会遇到问题,特别是在大型代码库中。
在Java 9上运行9以前的代码库时,您可能会遇到两种错误:一种源自您的代码库,另一种源自您的依赖。
对于源于代码库的错误,这个命令可能会有所帮助:jdeps。当指向一个类或目录时,该命令将扫描存在哪些依赖项,以及这些依赖项依赖的模块。
对于源于依赖关系的错误,您可寄希望您所依赖的包将会有更新的Java 9兼容构建版本。如果没有,你可能不得不寻找其他选择。
一个常见的错误是这样的:
How to resolve java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
这是Java抱怨找不到某个类,因为它已经迁移到一个模块,而对使用方(消费代码)不可见。这里(https://stackoverflow.com/questions/43574426/how-to-resolve-java-lang-noclassdeffounderror-javax-xml-bind-jaxbexception-in-j) 描述了几种具有不同复杂性和持久性的解决方案。
同样,如果在依赖项中发现此类错误,请进行项目检查。它们可能有Java 9构建版本供您使用。
JPMS是一个相当彻底的改变,需要时间来适应。幸运的是,并不着急,因为Java 8是一个长期支持版本。
也就是说,从长远来看,旧的项目将需要迁移,而新的项目将需要明智地使用模块,以期能够利用一些模块应允的好处。
7.小结
本文简要介绍了Java中的模块系统的相关技术知识,并做了实例性演示实战操作,动手练一把,基本就能窥得JPMS的总体特性了,后续有机会再深入讲解相关技术细节。
本篇就到这里了,关注一下,分享出去吧,谢谢.