另一个拼图观点

在过去的几周中,围绕即将发布的Java 9及其最著名的功能:Java平台模块系统JPMS展开了激烈的辩论。
–以项目拼图Jigsaw的名称而闻名。 模块系统以正式规范过程的形式引入Java生态系统 – JSR –需要由专家组以最终形式批准。 在该专家组的其他成员中,红帽和IBM的代表现已投票反对在他们认为尚未准备好生产的第一轮投票中拒绝Java的模块系统。

到底有什么毛病?

即使在今天,Java开发人员也对模块化非常熟悉。 像Maven这样的构建系统将代码组织为针对一组声明的依赖项进行编译的模块。 仅在运行时,这些模块才放到这些编译时模块边界消失的类路径上。 使用Jigsaw,模块路径可以作为此类路径的替代方式,JVM在运行时为其保留了此类编译时边界。 通过不使用此模块路径,应用程序应该像以前一样运行。 但是,这依赖于JVM内部的API的应用程序除外。 即使将类路径专门用于无法再访问内部Java API的情况下,Java标准库也始终作为模块的集合加载。

兼容性上的后一个限制引起了库和最终用户应用程序维护者的一些关注。 在这种情况下,最近的反对意见与这些关注关系不大可能会令人感到惊讶。 虽然提到兼容性方面的问题,但Red Hat和IBM都主要认为JPMS需要进一步扩展,以允许与现有模块系统(如JBoss模块和OSGi)更好地集成。

还有什么问题需要解决?

通过jar hell ,开发人员通常描述Java应用程序需要两个不同版本的库来满足不同的传递依赖关系的情况。 使用类路径,这是不可能的,因为一个版本的库会遮盖第二个副本。 如果是第一次加载给定名称的类,则系统类加载器将按其命令行顺序扫描jar文件,并加载其发现的第一个类文件。 在最坏的情况下,如果带阴影的jar文件包含一些链接到带阴影的jar类的专有类,则可能会导致科学怪人功能。 但是更典型的是,一旦触发了依赖于特定版本的功能,就会导致运行时失败。

使用OSGi和JBoss模块,可以部分解决此问题。 后面的模块系统允许每个库自己的类加载器加载一个库,从而避免了负责类路径的系统类加载器。 使用这种方法,可以通过隔离在单独的类加载器中来共存同一类的多个版本。 这样做,例如,两个库都可能都依赖于它们通常中断的Guava API的特定版本。 使用类加载器隔离,在加载依赖类时,任何库都会将调用委派给其所需的版本。

使用模块路径时,JPMS(当前)不应用此类类加载器隔离。 这意味着Java 9无法解决jar hell的问题。与使用类路径相反,JVM确实会检测到所描述的版本冲突并在启动时使应用程序失败,而不是推测意外的兼容性。 为了强制执行此约束,每个Java包名称现在都专有于特定模块或类路径。 因此,两个模块不可能共享一个包。 如果不打算公开私有软件包,则此限制也成立,这是Jigsaw评论家认为当前模块设计的另一个缺陷。

错过了逃脱地狱的机会吗?

为了使类加载器隔离有效,必须使同一模块的版本永不交互。 而且,尽管两个这样的版本当然永远不会直接交互,但是不幸的是,两个版本是不同模块的公共API的一部分比通常情况更常见。 例如,如果两个库返回番石榴的实例Function类型,每个模块的番石榴版本之间的版本冲突不能再使用的类加载器隔离,即使解决了Function型没有这些版本之间切换。 在运行时,任何已加载的类都被描述为其名称和类加载器的元组,但是由于两个类加载器现在提供了Function类型,应该解决哪个类型?

实际上,该描述的问题不能由模块系统解决。 相反,模块系统可以发现此冲突,并告知用户需要明确的解决方案。 这是通过JPMS的当前实现以及OSGi和JBoss模块实现的。 归根结底,只有通过以兼容方式发展API才能避免版本冲突。

拼图游戏太简单了吗?

尽管类装入器隔离模块系统仍然存在局限性,但当前反对Jigsaw的论点主要围绕此问题。 此外,拒绝Jigsaw的专家组成员指出,缺少对循环模块依赖关系的支持(“模块A依赖于B依赖于C依赖于A依赖”)以及在创建模块图后无法更改模块图。

从技术角度来看,当然可以添加这些功能。 实际上,Java 9已经附带了模块构建器API,该模块允许使用排他类加载器加载模块。 选择为模块路径保留单个类加载器没有技术限制。 相反,该决定被Oracle认为是JVM的负责任选择。 在深入探讨这些论点之前,我想声明我完全同意公司的推理。

类加载器隔离有什么问题?

如前所述,即使使用类加载器隔离,也常常无法避免手动版本管理。 此外,依赖具有不兼容版本(例如Guava)的通用API的库作者的确越来越掩盖了这种依赖性。 着色时,将库的代码复制到单独的名称空间中,从而使应用程序可以使用不同的名称而不是通过不同的类加载器来引用“其版本”。 当然,这种方法有其自身的缺陷,特别是当阴影依赖项使用JNI时。 另一方面,这种方法克服了在使用具有冲突的共享依赖项的库时,刚刚提到的类加载器隔离的缺点。 同样,通过隐藏公共依赖关系,库作者可以独立于部署方法而使用户免于潜在的冲突。

允许循环依赖既不会带来很大的技术挑战。 但是,循环依赖关系很少见,像Maven这样的许多构建系统都不支持它们。 通常,可以通过将至少一个模块分成实现和API来将循环依赖关系重构为非循环依赖关系。 在这种情况下,如果某个功能似乎很少引起人们的普遍关注,那么我认为角落情况并不能证明其附加功能是合理的,尤其是当类路径仍充当备用功能时。 而且,如果最终决定是错误的,则可以在以后的版本中始终启用循环依赖关系。 但是,取消此功能将是不可能的。

最后,动态模块提供的功能可能对多个应用程序有用。 根据我上一个项目的经验,当您需要动态地重新部署具有生命周期的模块时,OSGi是一个很好的选择。 也就是说,大多数应用程序都是静态的,没有充分的理由使用它。 但是,通过添加对动态模块图的支持,此功能的复杂性将转化为JPMS。 因此,我认为暂时不使用此功能,等到更好地理解其用途是正确的决定。 自然,可访问的模块系统会提高采用率。

兼容性至上

这种不兼容性是否意味着OSGi和JBoss模块的终结? 当然不是。 恰恰相反,标准化模块描述符的引入为现有模块系统提供了机会。 使用OSGi时,缺少描述包的清单标头是主要的痛点之一,因为大量库没有考虑专有模块描述符。 通过引入标准化的模块描述符,现有的模块系统可以通过使用后一个描述符作为模块描述的辅助来源来减轻此限制。

我毫不怀疑红帽和IBM出于最佳意图拒绝了JSR。 同时,我不能同意对模块系统缺乏覆盖面的批评。 在我看来,现有的变更对于Java生态系统的采用来说是充满挑战的,尤其是最后一刻引入类装入器隔离可能会带来意外的意外。 有鉴于此,我发现针对拼图的当前状态提出的论据不一致,因为它批评了向模块过渡的复杂性,但也要求对其进行扩展。

没有完善的模块系统

我个人认为,目前的JPMS提案面临两个重大挑战。 不幸的是,由于最近的讨论,它们成为了背景。

自动模块

如果没有模块描述符,则模块化代码只能以所谓的自动模块的形式引用非模块化jar文件。 自动模块没有任何限制,并由其jar文件命名。 这对于最终用户应用程序的开发人员非常有用,这些用户永远不会发布其代码以供其他应用程序使用。 但是,库开发人员确实缺少一个稳定的模块名称来引用其依赖的自动模块。 如果发布,它们将依赖稳定的文件名来获取依赖关系,这很难假设。

对于采用Jigsaw而言,这意味着一种自下而上的方法,其中任何库作者只能在所有相关代码都已经模块化之后才能对其软件进行模块化。 为了简化过渡,添加了一个清单条目,该条目允许发布具有稳定自动模块名称的jar,而无需模块化代码甚至迁移到Java9。这允许依赖此第一个库的其他用户使用具有稳定名称的库。对他们的代码进行模块化,从而突破了自下而上的要求。

我认为让库维护者在迁移其代码以完全使用JPMS之前声明一个明确的模块名称非常重要,并且我认为这是处理此问题的一种充分方法,这不可能提供更好的解决方案。

反思和可及性

使用Jigsaw,不再允许使用反射来访问非公共,未导出的成员,这是许多框架当前所认为的机会。 当然,设置了安全管理器后,即使在当今的Java版本中也无法进行这种访问,但是由于很少使用安全管理器,因此无需过多考虑。 对于Jigsaw,这种默认设置是相反的,在这种情况下,需要显式打开用于这种反射访问的包,从而影响许多Java应用程序。

总的来说,我认为Jigsaw的封装比当前的通用开放性更好。 如果我想让Hibernate访问我的bean,则JPMS允许我仅通过合格的出口将bean打开到Hibernate。 使用安全管理器,即使不是不可能实现,也很难控制这种细粒度的访问。 但是,这种过渡会带来很多麻烦,并且许多库的维护不够积极,无法适应这些新要求。 因此,添加此限制肯定会杀死某些原本可以提供价值的库。

此外,仍然存在一些反射的用例。 对于模拟库Mockito(我帮助维护),例如,我们需要一种在任何类加载器中定义类的方法。 这过去只能通过使用内部Java API来实现,而目前还没有其他选择。 由于Mockito仅在测试环境中使用,因此在这种情况下,安全性无需关注。 但是,由于sun.misc.Unsafe开放性,我们已经依靠该开放性来实例化模拟类而无需构造函数调用,因此我们可以通过使用其直接内存API更改其可访问性来简单地打开这些API。

当然,这在接下来的几年中还不够好解决,但是我坚信可以在完全删除不安全类之前解决这些问题。 作为一种可能性,可以使用需要在命令行上明确解决的测试模块来扩展JVM,并允许这种扩展访问。 另一种选择是要求任何测试运行程序都附加Java代理,因为它们具有突破模块障碍的能力。 但是就目前而言,任何维护的软件都有机会解决其非标准Java使用问题,并在未来几年继续讨论缺少的API。

寻找共识

考虑到社交焦虑的计算机呆子的刻板印象,软件开发可能是一件相当感性的事情。 甲骨文一直以来都是Java开发人员所讨厌的公司,当前的讨论在一定程度上推动了这一潮流。 但是,从Java作为一种语言和平台的成功来看,我确实认为Oracle值得称赞的是它在管理方面的客观出色表现。 考虑到未来的成功,当今破解软件是一项微妙而无济于事的任务。 任何重构正确但复杂的代码的人都应该同情这个挑战。

拼图项目经常因不必要的努力而受到批评,我承认这种想法已经超出了我的想法。 但是,归功于模块系统,像CORBA或RMI这样的自重组件最终可以从JVM中删除。 随着模块化Java应用程序大小的隐式减小,JVM对于在容器化应用程序和云计算中的使用变得越来越有吸引力,这对于Oracle的市场策略而言肯定不是巧合。 虽然当然可以将这种工作进一步推迟到更高的Java版本,但是JVM必须在某个时候解决功能的删除。 现在是一个好时机。

为了简化即将到来的过渡,将重大更改降低到最小很重要。 因此,我坚信扩展Jigsaw的范围并不符合更广泛的Java社区的最大利益。 最近投票的许多否决票都要求有关各方就悬而未决的问题达成共识。 不幸的是,在只有一方放弃立场的情况下才能达成共识的情况下,可以实施或放弃所讨论的功能。

考虑到典型的Java应用程序,我希望Oracle不会通过范围扩展来满足需求,只是为了确保对Jigsaw JSR进行成功的投票。 相反,我想呼吁那些对JSR表示反对的专家组成员重新考虑他们对整个Java生态系统需求的投票,因为现有企业模块解决方案的需求只是众多因素中的一个因素。 随着Java从商业应用程序到低延迟系统的广泛使用,自然而然地,不同的各方会为平台的发展确定不同的优先级。 我相信Oracle已经为服务于大多数用户的模块系统找到了共同点。

翻译自: https://www.javacodegeeks.com/2017/05/yet-another-jigsaw-opinion-piece.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值