和 .project 文件说“再见”—— VS Code Java 1.1.0 背后的故事

2021-12-02 14:10:28 +08:00
 winffee

Language Support for Java 1.1.0 版本包含了一项重要更新:现在插件在导入新的 Java 项目时,项目元数据文件(.project ,.classpath ,settings 等)默认将不再生成于项目路径下。这一问题 自 2018 年被记录至今已有超过三年的时间。本文旨在记录并分享我们解决这一问题的过程和最后的解决方案。

悬在头顶的"达摩克利斯之剑"

随着 VS Code Java 的功能逐渐丰富,用户数量也在稳步上升。但是由于 Java 插件在导入项目时,会在项目目录下生成元数据文件的问题,我们得到了不少的 1 星差评。可以预见,随着用户基数增加,因这一问题而造成的差评数量也会增加。这就如同一把悬在我们头顶的"达摩克利斯之剑",如果不及时解决,问题随时都有可能爆发。

其实这并不是我们产品组不想彻底修复这一问题,根本原因需要从 Java 语言服务的架构说起:

JDT Java Language Server 架构示意图

VS Code Java 项目背后所采用的 Java 语言服务的正式项目名称是 Eclipse JDT Language Server™,由微软和红帽联手开发。在上面的项目架构图中可以看到,我们在实现中复用了 Eclipse 的一些模块,而这些自动生成的元数据文件也正是由其中一些上游模块所产生。在 Eclipse 的讨论区中可以找到一条相关的讨论帖子。这条帖子的创建时间甚至可以追溯到 2004 年。由于在实现时,这些元数据文件的路径就已经作为常量被硬编码在了代码里,这些常量又被各个不同的 Eclipse 模块甚至是插件引用,经年累月下来这一问题从某种意义上已经成为了"历史包袱"。

考虑到改变上游模块的行为包含了太多的未知和不确定性,在过去我们尝试给用户提供一些变通方法,比如让这些元数据文件在 VS Code 的文件浏览器中隐藏,并引导用户将他们添加至 .gitignore 当中。但从用户的反馈来看,这些方式并没有让用户感到满意。为了能够彻底解决这个已经困扰了我们以及用户三年多之久的"顽疾",我们在今年下半年决定再做一次尝试,希望能将其"根治"。

方案一:使用 Symbolic Link (失败)

我们最先想到的方法是使用 Symbolic Link。在导入项目时,可以将被导入的项目通过 Symbolic Link 的方式链接到一个用户看不到的地方,从而让元数据文件生成在链接后的路径下。但很快这一方案就遇到了问题——在某些操作系统下创建 Symbolic Link 需要特定的权限,否则会抛出 FileSystemException ,这显然不是我们想要的效果,因此这个方案马上被否决了。

方案二:使用 Eclipse Linked Resources (放弃)

和 Symbolic Link 的思路类似地,我们还可以选择使用 Eclipse Linked Resources:

Linked Resources : Linked resources are files and folders that are stored in locations in the file system outside of the project's location.

上文是 Linked Resources 的一段官方定义,它可以作为项目的一部分,但又允许存储在项目路径之外的其他位置。在 VS Code Java 中,我们对于 Unmanaged Folder (无构建系统的项目),就是通过 Linked Resources 机制将这些元数据文件隐藏的,它的实现原理如下图所示:

Unmanaged Folder 实现原理

可以看到项目的实际路径放在了 Language Server workspace storage 中,用户通常并不知晓这一路径,同时在 .project 文件里我们定义了 Linked Resources 的目标路径,也就是用户在 VS Code 打开的文件夹位置,它作为项目的一部分,会像其他项目一样参与到构建过程当中,其开发体验是类似的。

相同的原理可以应用到 Maven 项目和 Gradle 项目的导入过程当中来解决这一问题,因此,我们在 M2E 模块上进行了一些实验。M2E 模块在 Java 语言服务中负责 Maven 项目的导入,通过改动模块中的相关代码,并利用 Linked Resources 机制就可以将元数据文件生成到项目路径之外的地方。

最终的实验结果是可行的,但是这套方案的缺点也非常明显:

考虑到上述原因,团队在经过讨论之后决定暂时放弃 Eclipse Linked Resources 方案,并继续寻找更优的解决办法。

发现"银弹"

放弃第二套方案还有另一个原因:Eclipse 自发布至今二十载,在保证稳定运行的同时,可以不断地增加新的功能且提供了出色的拓展能力,在这背后一定蕴含了优秀的架构设计和可拓展性。直觉上让我们觉得应该还会有更优雅的解决办法。

因此,这一次我们直接从 Eclipse 底层文件系统入手分析,并最终发现了一枚解决问题的"银弹":File System Provider 和 FileStore (注:虽然在软件工程领域,人们的共识是没有银弹,不过对于这一特定的问题,我们确实找到了一种比较"奇巧"的解决办法)。

Eclipse 工作空间结构与 FileStore

Eclipse 在运行过程中会为整个工作空间维护一颗树形结构,树的节点代表了文件系统中的文件或目录,同时还保存了文件的一些重要信息,如修改时间等。

Eclipse 底层通过 FileStore 类将这些节点和文件系统中的文件进行关联。FileStore 类还有一个重要特性:如果映射的对象是单个文件,那么 FileStore 还会负责提供这一文件的输入输出流。

这一特性为问题的解决带来了非常重要的思路:只要能够将元数据文件的输入输出流重定向到项目目录之外的位置,问题也许就能得以解决。带着这个假设,我们又发现了另一个关键线索:File System Provider 。

方案三:File System Provider

File System Provider 是 Eclipse 平台对外开放的一个扩展点,它允许开发人员实现一个 Eclipse 文件系统接口( org.eclipse.core.filesystem.IFileSystem ),并将其注册到扩展点上,用以处理具有特定 URI scheme 的文件请求。

于是我们从 File System Provider 这一拓展点入手,继承并覆盖了 Eclipse 默认处理 URI scheme 为 file 的文件系统,通过覆写其中的一些方法,让文件系统在处理元数据文件时,将文件路径重定向到项目路径之外的地方进行读写。相比于方案二,这一套方案的优点在于:

当然这个方案也并非完美,因为它要求其他模块通过 Eclipse 提供的 API 进行对元数据文件的读写操作。我们在实现过程中就发现上游 Buildship 在处理元数据文件时直接通过JDK中的文件 I/O API 进行读写,为此我们提交了一份变更请求将相关操作迁移到了 Eclipse API 上。

总结

在权衡了利弊之后,我们最终选取了第三套方案并解决了这一困扰了 VS Code Java 用户三年多时间的问题。虽然最终的实现并不复杂,但探寻答案的过程却非常具有戏剧性。

最后特别感谢 Eclipse Platform 项目成员 Mickael Istria 以及 Alexander Fedorov 。在问题讨论的过程中他们给予了非常有用的建议,对问题的解决起到了非常关键的作用。

2352 次点击
所在节点    Visual Studio Code
8 条回复
fsdrw08
2021-12-03 09:10:43 +08:00
vscode 能做 JAVA8 的开发吗?本地开发
kowgarnett
2021-12-03 13:37:02 +08:00
@fsdrw08 能写,能 build ,能测试,但是对 ant/gradle 一类的支持不如 intellij ,而且没有 remote debugger
jdneo
2021-12-06 10:00:14 +08:00
@kowgarnett remote debug 是支持的,直接通过 jdwp attach 可以,借助 vscode remote 插件也可以。
RedBeanIce
2021-12-06 13:38:49 +08:00
vscode 冲啊
byte10
2022-01-21 11:41:39 +08:00
@jdneo jdwp attach 有严重的问题,调试非常的卡,不是一秒一步的问题,而是每走一步要等 3-5 秒,我的天。这个你可以试试看,跟作者反馈过了,好几个项目都不行。原因未知。
jdneo
2022-01-24 12:12:04 +08:00
@byte10 因为 jdwp 是一个很“啰嗦”的协议,一次单步背后会有大量的 jdwp 请求发出,如果本地和远程机器之间的网络延时比较大的话,就会感到很卡。

所以确实这种情况下 vscode remote 会更好一些。
byte10
2022-01-24 13:30:42 +08:00
@jdneo 内网的,不是网络问题,idea 说不上顺畅,至少还能动起来,变量值那些都会慢慢加载进来,vscode 直接卡主不动😂,前面几步能走下去,多走了几步就卡了
jdneo
2022-01-25 13:45:35 +08:00
@byte10 我猜测可能 idea 是做了某些优化的。VS Code Java 这边目前就是最朴素的实现,每个 jdwp 请求彼此之间都是同步的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/819527

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX