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和hibernatejava.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生态系统看作一个整体,研究现有工具和方法与模块系统的关系以及它们的未来,是非常值得的既然你已经知道了理解它所需要的一切,仍不能放过这些问题,为什么不现在就翻到那一节呢?
(图片来源网络,侵删)
0 评论