第一个阿里教你程序员模块(模块应用程序系统第一个程序员)「阿里的第一个程序员」

JDK被模块化后看上去很不错,但你自己的代码呢?如何将之模块化?这是个非常简单的问题
唯一要做的,就是在源代码目录中增加一个名为module-info.java的文件作为模块声明,并且在其中填入模块名、对其他模块的依赖,以及组成公有API的包
看上去my.xml.app模块使用了平台模块java.base和java.xml,并且导出了com.example.xml包
到目前为止一切顺利
现在将module-info.java与其他源代码一起编译成.class文件并且打包进一个JAR(编译器和jar工具会自动地正确处理它们)
非常好,你已经创建了第一个模块
一 模块系统实战要启动XML应用程序并观察模块系统的运转,可以执行如下命令
模块系统从此处开始接管
它需要采取如下步骤来摆脱你在前面文章中所看到的“大泥球”困境
(1) 自启动
(2) 验证所需的模块都存在
(3) 构建应用程序架构的内部描述
(4) 启动初始模块的main函数
(5) 在应用程序执行过程中持续运行以保护模块内部
图1-12包含了所有步骤
但是请不要冒进,而要逐个理解它们
图1-12运行中的Java平台模块系统
在启动过程中它完成了大多数工作:在启动之后,它在构建模块图时确保所有模块都存在,此后将控制权交给运行中的应用程序
在运行时,它强制保护每个模块的内部
01. 加载基础模块模块系统只是代码,并且前文提到一切皆模块,那么哪一个包含了JPMS?答案是基础模块java.base
像“鸡生蛋蛋生鸡”问题一样,模块系统和基础模块相互引导启动
基础模块也是JPMS构建的模块图的第一个节点
这就是下面要做的事情
02. 模块解析:构建一个描绘应用程序的图你输入的命令以--module my.xml.app结尾,它会告诉模块系统my.xml.app是应用程序的主要模块并且模块解析需要从这里开始
但是JPMS在哪儿能找到这个模块呢?这正是--module-patmods大展身手的地方,它可以告诉模块系统,应该在mods目录中找应用程序模块,这样JPMS会尽责地从那里寻找my.xml.app模块
但是目录并不包含模块,它们只包含JAR
所以模块系统扫描mods目录中的所有JAR并且寻找它们的模块描述符
此例中,mods目录包含my.xml.app.jar,并且它的描述声称其包含名为my.xml.app的模块
这就是模块系统一直在寻找的东西
JPMS创建了my.xml.app的一个内部描述并且将之添加到模块图中——到目前为止,它还没有和任何其他事物产生关联
模块系统找到了初始模块,下一步是什么?搜索依赖
my.xml.app的描述符声明它需要java.base和java.xml模块,JPMS又如何找到它们呢?首先,java.base模块是已知的,所以模块系统可以在my.xml.app和java.base之间添加一个连接,即模块图中的第一条边
接下来是java.xml模块
它的第一个单词是java,这就告诉模块系统它是一个Java平台模块,所以JPMS不会在模块路径中寻找它,而是会搜索模块仓库
找到java.xml模块后,JPMS会将其添加到模块图中,并将my.xml.app与之连接
现在模块图中有3个节点,但只解析了两个,java.xml模块的依赖关系仍旧不明,因此JPMS将继续寻找这些依赖关系
在发现java.xml仅依赖于java.base后,模块解析的工作完成
从my.xml.app和无所不在的基础模块开始,这个过程构建了一个具有3个节点的局部模块图
如果JPMS找不到所需的模块,或者遇到任何歧义(比如两个包含同名模块的JAR),它将退出并提供错误信息
这意味着人们可以在启动时就发现问题,从而避免在将来应用程序运行中的任意时间点出现错误,导致应用程序崩溃
03. 启动初始模块回想一下这个过程是如何开始的?是的,输入以--modulemy.xml.app结尾的命令
接着模块系统完成它的核心功能之一——验证所有必需的依赖是否存在,然后将控制权移交给应用程序
初始模块my.xml.app不仅是模块解析的起点,还必须包含main(public static void main(String[]))函数
但是在启动应用程序时,不一定需要指定包含该方法的类
此处不指定是因为在将类(.class)文件打包成JAR的时候已经指定好了该主类的位置
这一信息被嵌入至模块描述符中,这样JPMS就可以对它进行读取了
由于使用了--module my.xml.app但未指定主类,因此模块系统希望在模块描述符中找到该信息
幸好,它找到了主类并在其上调用了main函数
应用程序启动了,但是JPMS的工作还未结束
04. 保护模块内部即使应用程序启动成功,模块系统也需要持续运行,以实现其第二个基本功能:保护模块内部
还记得my.xml.app的模块声明中的exports my.xml.api吗?这一行及其类似的内容就是该功能发挥作用的地方
每当一个模块首次访问另外一个模块中的类型时,JPMS就会验证以下3个条件是否满足
1 被访问的类型必须是公有的(public)
2 拥有该类型的模块必须已导出对应的包
3 在模块图中,访问模块必须连接到被调用模块
所以当my.xml.app模块首次使用javax.xml.XMLConstants时,模块系统将检查XMLConstants是否是公有的(√)、java.xml模块是否导出javax.xml包(√),以及在模块图中my.xml.app是否已连接到java.xml(√)
三者都检查通过后,my.xml.app才能使用XMLConstants
这种验证方式弥补了之前讨论的“大泥球”方法的严重缺陷:无法区分工件内部的代码与可公开使用的代码
有了exports关键字,模块就可以清晰地定义哪些API是公有的,哪些是内部的,并且可以依赖模块系统来保证这些选择得以实现
05. 一个更复杂的示例作为一个并不简单的示例,图1-13展示了前两节介绍的ServiceMonitor应用程序的模块图
它包含4个JAR——monitor、observer、statistics和persistence,以及两个依赖模块——spark和hibernate
java.xml和java.base等JDK模块也清晰可见,因为应用程序也依赖于其中的一些模块
图1-13ServiceMonitor应用程序的模块图非常类似于图1-6中的体系结构图
该图显示了包含应用程序代码的4个模块、用于实现其功能集的两个库以及JDK中涉及的模块
箭头描绘了它们之间的依赖关系
每个模块仅列出了一部分导出包图1-13与图1-6的对比非常引人注目,图1-6描绘的是ServiceMonitor的JAR文件间的依赖关系,展示了我们对如何在工件级别上组织应用程序的理解,而图1-13展示了如何从模块系统角度看待应用程序
它们非常相似,这表明模块系统可以很好地表达应用程序的体系结构
二 非模块化项目基本不受影响现有项目(尤其是具有大型代码库的项目)的开发人员,可能对迁移路径感兴趣
虽然在其他模块系统中,迁移通常意味着“要么全盘接受,要么彻底放弃”——为了能够使用,一切都必须是模块,但是在JPMS中情况有所不同
为了保持向后兼容性,在Java 8或更早版本的类路径上运行的常规应用程序,在Java 9上的行为必须一致
因此,非模块化的应用程序必须能在模块化JDK之上运行,这意味着模块系统必须处理这样的情况
事实确实如此
前文提到模块系统能处理尚未转化为模块的JAR,这正是因为向后兼容性
虽然迁移到模块系统是有益的,但这不是强制性的
因此,类路径的工作方式仍然与在Java 8或更早版本中相同,它可以用来为编译器和JVM指定JAR或普通类文件
就连类路径上的模块也会像非模块化JAR一样运行
这里的基本假设是,类路径机制负责访问“大泥球”内的工件,正如1.3节所述
与此同时,一个新的概念诞生了:模块路径(module path)
这里的基本假设是将所有工件视为模块
有趣的是,即使是对普通的JAR也是如此
要点 类路径和模块路径的共存以及它们对普通工件和模块化工件的不同处理方式,是大型应用程序向模块系统逐渐迁移的关键
另一个对于模块系统,尤其是遗留项目而言十分重要的方面,就是兼容性
JPMS的诞生涉及大量底层修改,虽然绝大多数修改严格保持向后兼容,但有些与现有代码库的交互很糟糕
1 对JDK内部API的依赖(比如引用sun.包)将会导致编译时错误或运行时警告
2 JEE的API必须手动解析
3 不同工件下相同包中的类会造成问题
4 紧凑配置文件、扩展机制、授权标准覆盖机制以及类似功能已被删除
5 运行时图像布局发生了显著的改变
6 应用程序的类加载器不再是URLClassLoader
最后,无论应用程序是否模块化,在Java 9或更高版本上运行都有可能出现问题
第6章和第7章将致力于识别和克服这些最常见的挑战
此时,你可能会有下列疑问
1 Maven、Gradle以及其他类似软件不是已经管理好依赖关系了吗?2 开放服务网关协议(Open Service Gateway Initiative,OSGi)呢?为什么不直接用它?3 在微服务普遍流行的时代,模块系统是否矫枉过正?提出这些疑问是正确的
没有任何技术是一座孤岛,而将整个Java生态系统看作一个整体,研究现有工具和方法与模块系统的关系以及它们的未来,是非常值得的
既然你已经知道了理解它所需要的一切,仍不能放过这些问题,为什么不现在就翻到那一节呢?
第一个阿里教你程序员模块(模块应用程序系统第一个程序员)
(图片来源网络,侵删)

联系我们

在线咨询:点击这里给我发消息