把 classpath 留在后视镜里:Netflix 发布基于 Java 模块系统的新一代 CLI 工具 ja

引入可组合、模块系统原生且代理友好的命令行工具,用于现代 Java 开发
作者:Danny Thomas,JVM生态系统团队
最近对Java语言的研究铺设入口匝道它使得使用完整语言和平台启动并进化 Java 程序变得前所未有的容易。在这条入口的尽头,是 Java 成熟的构建与依赖管理生态系统,能够将软件承载到极其大规模和复杂的规模。
该生态系统通过开发强有力的项目、依赖和构建模型而达到成熟。当Java 模块系统这些模型已经为开发者提供了极佳的服务。模块描述符因此成为项目的另一种描述,需要保持一致。
我们很高兴宣布预览以及以及其可组合工具家族,这些工具基于 Java 模块系统的能力,提供现代命令行开发体验。我们将模块描述符变成项目的完整描述,依赖版本自然地置于需求指令和通过文档标签提供的模块元数据旁边:
/**
* @mainClass com.example.application.Main
*/
module com.example.application {
requires com.example.framework; // @1.2.3
}
再加上你在其他语言中习惯的命令行人体工学设计,创建和使用Java模块从未如此简单。
可组合工具
Java开发者长期以来一直非常依赖图形工具。IDE格式化源代码,在声明和使用之间导航,呈现API文档,并维护项目的编译视图。这种体验非常完整,以至于Java几乎不需要通过小型、可组合的命令行工具来暴露相同的能力。
当编码代理使用Java语言时,这些空白会很快显现出来,代理经常难以找到依赖、文档和源代码。
JA只提供命令行的人体工学和工具编排,每个功能都由独立工具支撑。你不需要采用JA也能享受这些工具的好处,你可以以任何你选择的方式编写它们:
- 吉格执行模块版本解析、编译和汇编,输出标准模块系统参数供其他工具使用。它也是与 Maven 仓库之间的桥梁,提供独立模块代理和发布命令
- JFMT格式源代码使用Java 编程语言的代码规范,适配现代Java语言。避免了代理编写代码中常见的空白、缩进、导入顺序和限定类引用
- 吃吧提供源代码感知的符号搜索,提供 grep 风格的界面,帮助理解类文件及其相关源代码。允许编码代理无需索引、LSP 或 MCP,同时通过争论文件契约
- jdocserver提供本地可浏览的 API 文档
这些项目利用平台的工具发现和执行功能,设计为与标准工具一起安装在你的 JDK 中。它们都实现了工具或工具提供者,允许它们在进程中运行。
这也是 ja 的工具发现和执行模型。我们用选项检查器以及可选的自定义元数据,用于发现支持哪些模块系统选项,从而代表工具解决参数。
这为源路径模块与标准JDK工具如jdeps、jlink和jshell无缝衔接。
Maven 作为基金会
在最近对Maven Central上1000件最受欢迎工件的调查中,只有232件有明确的模块定义,另外248件宣告了自动模块名称。其余520件则没有表达对Java模块名称的看法。
模块系统也没有区分命名空间和模块名称,因此模块优先工具需要解决模块命名和在现有仓库中的位置问题。
幸运的是,Maven Central 已经为已发布的工件提供了经过验证的命名空间。出版商证明了对反向域名组ID的控制,反映了Sonatype长期以来的主张公共仓库中的命名空间.
我们利用这些惯例建立一个规范的 Maven 模块坐标,将可验证的 DNS 命名空间与完整模块名称进行比较,例如 pkg:maven/com.netflix/com.netflix.tools.ja。
对于现有模块,作者选择在典范坐标发布单个 Maven 重定位 pom,以便发现原始坐标。
当两者都不可用时,候选者会使用常见的Maven工件约定从命名空间根开始步行,这些约定是从模块名称推断坐标。我们还捆绑了一份最流行的模块别名简短列表,这些模块不使用反向DNS模块名,但建议作者始终其模块命名空间.jig 中的模块代理采用基于文件名的模块命名惯例,呈现解析模块,使得即使是没有稳定名的自动模块在这些工具中使用时也安全。
这些约定和定位策略允许仅用模块名称和版本即可发现大多数现有工件。
默认完整性
由于类路径的广泛使用,ALL-UNNAMED 不幸地在 Java 访问选项中变得常见。它掩盖了应用程序因允许此类访问而产生的技术债务根源,随着 Java 的发展,其影响也日益显著默认的完整性.例如,准备进入最终阶段要求应用程序明确授权允许变异最终字段的模块。
我们允许运行时访问需求声明为模块元数据,并与模块描述符一同贯穿模块生命周期。例如,一个库可能记录其所需的访问:
/**
* @enableFinalFieldMutation com.example.framework
*/
module com.example.framework {
}
然而,使用应用程序仍然处于控制权中,并且必须明确授权该框架,才能在运行时提供:
/**
* @mainClass com.example.application.Main
* @enableFinalFieldMutation com.example.framework
*/
module com.example.application {
requires com.example.framework; // @1.2.3
}
ja 的命令行界面允许将依赖和授权合并:
ja require com.example.framework@1.2.3 \
--enable-final-field-mutation com.example.framework
没有该授权,依赖关系解决将失败,且访问要求未满足。原生访问在@enableNativeAccess上遵循相同模型,且支持合格的导出和开放。
模块完整性通过在 module-info.hash 文件中对已解析的二进制依赖的持久哈希来保证,序列解析会验证这些哈希并拒绝发生变化的伪造。
我们还比近期对注释处理器安全性的改进更进一步,将注释处理视为显式代码生成步骤。最终生成的源代码与常规模块源代码并列,使其在代码审查中可见,并允许模块组装而无需执行生成器代码。
把模块设为默认配置
我们认为每个 Java 项目都应该是模块化的,无论你用的是哪种构建工具。如果你是库作者,自动生成模块,我们建议你避免拆分包,直接生成显式模块。
你今天就可以开始使用我们的工具,我们的安装指南.
最初发表于Netflix 技术博客在Medium上,人们通过突出和回应这个故事,继续讨论。