今天,Oracle的Mark Reinhold发布了一份关于即将纳入Java SE 8平台的Java模块系统的公共草案需求供评论。所给要求相当高级但全面,其中许多要求与我们的JBoss模块系统的目标和规范相吻合,这不仅是对我们设计强有力的验证,也是我认为Java平台未来的好兆头。
然而,发布的要求中包含了一些值得关注的事项,也有一些我认为根据我在实施JBoss模块环境以及将该环境应用于JBoss AS 7和6(我们曾基于实验性质进行过)的经验,以及来自负责JBoss OSGi的同事的反馈,应该进行改变的事项。这些项目规模庞大且差异很大,它们教会了我们很多关于Java模块实际性质的知识。
由于这是一份很大的文档,我将分阶段进行评论,看看我们最终会走到哪里。
要求
可以在这里找到的文档分为几个部分,我将尝试按顺序进行讨论。
基础:版本
基础段落中的所有条目都表明Jigsaw项目正走在正确的道路上。然而,我发现有几个部分值得关注。
所有阶段的解析 - 建设时间、安装时间和运行时模块解析和链接都必须得到支持。UNIX有三种模型,而且40年后仍在使用。
与以下段落相联系
所有阶段的忠实度 - 库或应用程序看到的模块集必须在构建时间、安装时间和运行时通过相同的算法计算得出。
以及这一条
版本控制 — 模块系统必须支持常见的版本字符串方案 [...]。在声明模块依赖时,必须能够指定允许的版本范围。
这些看似合理的陈述隐藏了一个特别棘手的怪物;即构建可重现性。在构建时,一个工件(使用Maven术语)部分通过其版本来标识。而这个身份具有特定的含义。如果我检出源代码控制中的jboss-cobwobble 1.2.3版本并构建它,我应该得到一个与任何人检出并构建相同版本的相同项目所得到的工件等效。
然而,这意味着此类构建不能使用版本范围。根据定义,这为两个构建相同项目的双方提供了不同的输出方式,从而模糊了版本的含义。在第一段中,Mark强调了与共享库模型的相似性,并建议遵循该范例的类似做法,然而存在一个关键区别,即当构建针对共享库时,链接要小得多。一个项目使用的编译库的改变通常不会影响该项目的编译结果,而Java中这样做则要容易得多,因为类文件格式比共享对象符号表更丰富。
因此,允许(最好是要求)构建时使用特定的依赖版本非常重要,并且只在安装和运行时允许版本范围,此时版本范围更适合。
另一个非常重要的考虑因素是,在工件产生后,其依赖的版本随时间的推移而演变,因为各种新版本被发布。因此,具有封闭上限的依赖版本范围不能是模块内部元数据的一部分,否则随着时间的推移,模块可能需要重新打包以适应新的版本兼容性。我个人的建议实际上是,模块仅支持版本范围的下限,以对包/运行时进行约束。
基础知识:模块的目标约束
我承认我不理解这个段落的用途,内容如下
模块的目标约束 — 在特定的目标平台上,必须能够声明只能安装具有特定属性的模块,例如,特定的作者、出版商或许可证。解析算法必须忽略任何不满足目标平台模块约束的模块。
如果这个目的是为了安全,我非常支持,那么我认为唯一合理的做法是将模块加载约束为签名模块。否则,如果目标是简单地创建一种机制,使管理员彼此烦恼,那么我想这符合要求;可以通过更改元数据来绕过这种限制,这意味着它既不提供安全,也不提供便利。
总的来说,我认为这个机制也冒着(希望)围绕此增强出现的模块生态系统碎片化的风险。例如,我认为Perl从单个模块库中受益匪浅,该库对贡献者相对较少限制;然而操作系统发行商(你认识哪些?)已经建立了这些模块的发行最佳实践。当然,今天的Java开发者经常使用Maven中心存储库,并期望它包含“一切”在合理的位置。然而,通过创建这些过滤机制,解决了许多发行商的问题,但这发生在错误的地方,并可能导致一些潜在的非常令人惊讶的行为(我现在就能看到FAQ:《我安装了一个模块,但Java说找不到...这是怎么回事?》)。
基础知识:本地代码
这一节基本上说的是模块需要以某种方式支持本地代码,虽然这似乎意味着本地库必须位于模块内部,但我认为这可能是多余的(尽管当然它必须存在于模块的打包中,否则就没有实际安装的方法)。在JBoss Modules中,我们简单地要求本地库存在于文件系统支持的资源加载器中(与通常用于类的jar支持的资源加载器相反)。此外,文件系统资源加载器使用操作系统和硬件平台名称,以便打包包含多个平台本地位的模块(实际上如果我记得正确的话,这与Perl的DynaLoader做得很像)。
基础:包子集和共享类加载器
我对这些部分有一个严重的问题
包子集——为了支持平台模块化,必须能够通过多个模块提供在Java包中定义的类型,同时在运行时仍然由同一个类加载器加载。这是为了能够定义大型遗留包,如java.util的大小的CDC子集。
共享类加载器——为了支持平台模块化,必须能够声明在特定模块集中定义的类型必须由同一个类加载器加载。
我非常反对这种观点。一个模块,一个类加载器,这就是全部,结束。如果你正在将单个类加载器分割到多个模块中,你没有创建模块,你创建了一个带有多个JAR的类路径的单个模块,其中一些内容可能缺失,一些则不缺失,以及大量无用的复杂性,所有这些都为了享有“当然,我们模块化了这一点”的特权。它们之间没有任何隔离,只是Java语言提供的标准访问控制。如果没有对模块作为类加载器封装载体的强定义,整个想法就削弱了——可见性只在整体类加载器的层面上有意义的执行(是的,虽然使用不同的分割单位,如类,可能是可能的,但它肯定会有负面的性能影响)。
与其削弱模块的定义,我建议允许模块分部分打包,可以单独运输和安装——也许可以引入模块/子模块的概念,其中子模块的合同明确定义为与父模块的类加载器共享。换句话说,标签应该清楚地标记,换句话说。没有合理的期望一个包可以在模块之间分割,这样做无疑是违反最小WTF原则的。
安全:你在哪里?
尽管有一些段落提到了它,但没有具体的部分涉及安全,我认为这是一个相当严重的疏忽。我本人希望看到对标准安全提供者机制的改进,以便比我们今天拥有的机制更方便地适应模块和模块签名者,我之前已经就模块签名发表过评论,更不用说模块安全提供者发现了(好吧,这种事情目前列为非要求是公平的——但它在我的脑海中,你可以肯定,如果我有任何发言权,它会在JBoss AS 7.x中某个时候出现)。
总结,第一部分
这就是第一章的内容。总的来说,基础让我有一种感觉,在Jigsaw土地上,事情似乎更加可控……特别是,制动器显然正在缓缓地应用于实现,以便提出真实的需求,这是确保项目坚定地处于正确的行星表面的关键。问问我的同事,他们会告诉你我多么喜欢一个好的需求文档!
下次我会尽量覆盖更多内容,因为后续章节比《基础》部分要短得多。