2024年Clojure状态调查中分享您的想法!

欢迎!请参阅关于页面了解有关如何使用本服务的更多信息。

+2
Clojure
已关闭

嗨,在升级到 Clojure 1.11.0 时,我遇到了以下情况。在我们的应用程序中,有一些地方我们使用 danlentz/clj-uuid 库创建了 V5 (基于名称,SHA1 哈希) uuids。升级后,生成的 v5 uuids 不同。例如

(def ^:const +namespace+ #uuid "50d94d91-a1cf-422d-9586-4ddacf6df176")

(clj-uuid/v5 +namespace+ :some-keyword) 

;; Clojure 1.10.3
=> #uuid "d30e9c3c-ced2-534e-a6b8-ecf784fb0785"

;; Clojure 1.11.0
=> #uuid "a16f6719-952a-55b9-b71b-b15dd263665b"

经过一番试验和错误,我觉得是局部参数(即::some-keyword)导致了这种差异,因为在 clj-uuid/v5 函数中,它将关键字对象转换为字节数组,而这现在似乎不同了。(如果我用字符串而不是关键字作为局部参数,则生成的 uuid 在 Clojure 升级前后是一致的。)
生成的 uuids 用于下游系统,处理这种变化将会非常困难。有没有办法让我生成与之前完全相同的 uuids呢?

以以下注解关闭: 在 1.11.1 中修复

2 答案

0

你好。感谢你的报告。为了澄清——测试之间使用的 Java 版本是相同的,对吧?

是的,同一个 Java 版本的 (openjdk 版本 "17.0.2")
0

编辑

在这里重新梳理一下其他地方的对话,以归档记录——似乎使用关键字作为名称的本地部分将导致 clj-uuid 使用 Java 序列化将对象序列化为字节。

Clojure 1.11 在 Keyword 上进行了一些添加性的向后兼容的更改(以改善算术异常的报告),因此关键字之间的二进制序列化在 1.10 和 1.11 之间发生了变化。

我们不保证 Clojure 对象在不同版本之间的二进制可序列化性,因此这里期待这些会相同是不正确的。在 clj-uuid 中有几个方式可以处理这种特性,如果需要的话——通过在 UUIDNameBytes 协议中为 Keyword 提供特定的自定义序列化(https://github.com/danlentz/clj-uuid/blob/master/src/clj_uuid.clj#L557),或者通过依赖于 pr 到字符串再转换成字节,而不是二进制序列到对象流等。

我们可能会通过将 clojure.lang.Keyword 的 serialversionUID 设置为 1.10 时的值来“修复”这个特例(因为这些对象可能是二进制兼容的),我们也会考虑这一做法。但是即使我们这么做,我也建议使用更稳定的东西(比如字符串)作为 clj-uuid 本地名称的部分。

谢谢 Alex,讲得有道理。我会在 clj-uuid 上提交一个问题,看看是否存在前进的道路。
通过创建类似Keyword和Symbol的Java类(相同的不持久化字段)并在这两个类中将serialVersionUID设置为1.10版本,可能可以修复您特定的问题。我相信您然后可以(稳定地)复制序列化的字节数据。您可以在clj-uuid中重写协议以使用这些伪类进行Keyword序列化。您将永远会遇到这个破解方法,但它可能为您提供迁移的途径。或者,如果您有少量关键字命名空间,甚至可以将关键字到字节的映射硬编码。
谢谢Alex,我会试试看。出于好奇,如果Symbol类在1.11.0中没有改变,为什么需要创建它?
Keyword类有一个持有Symbol字段的字段,因此序列化将包括这两个类。
明白了,谢谢
大家好。是的,我确实希望为更广泛的对象(特别是关键字)特设一个稳定的字节数据转换。

为了更好地理解关于“伪关键字/符号类”的想法,这些是否可以在不共享相同的(冲突的)类名的情况下以相同的方式序列化?即使我们以相同的方式序列化(.writeObject),我认为对于java.io.ObjectOutputStream的读取似乎很失望——它序列化“对象的类、类别签名以及所有非瞬态字段的值”。我想我们可能还需要一个专门的ObjectOutputStream吗?

我一直在查看命名空间关键字的字节数据序列化。可能可以重建这个,但至少一开始来看,它不像只是直接丢弃关键字名称和命名空间的字节那样明确。
是的,我认为它需要相同的类名。虽然可以在类加载器中加载不同的类(即使是动态类加载器),但这将是一个非常复杂的解决方案。
我已经完成了一些工作,但不得不复制clojure.lang.Keyword类(正如你所说,它确实需要具有完全相同的类名),并添加了一个serialVersionUID(其值与1.10.0版本中的类自动生成的值相等)。

我开始尝试使其具有不同的类名,并在序列化逻辑中将它覆盖为“clojure.lang.Keyword”,以便自定义类仅用于uuid生成/不冲突——但并未取得太大成功。例如,这个https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/io/ObjectStreamClass.java#L708是该名称作为ObjectStreamClass的一部分被写入的地方。
还有一个在ObjectOutputStream上的 .writeObjectOverride 方法... 但是重写默认的writeObject并不很有吸引力。
by
顺便说一句,我们可能要做一个1.11.1版本,并将Keyword和ArraySeq的serialVersionUIDs固定回到1.10.3版本。以防这有助于决定延迟升级路径。
by
关于uuid的延迟升级路径,对最佳处理方式有何看法? 是否强制执行不兼容(但稳定)的序列化作为可选方案? 默认吗?
by
我认为除了在库中放置兼容性说明之外,没有很好的标准方法来处理这个问题。你可以使用版本条件语句做些事情,但这所有的选项都很糟糕,并且会影响性能。
Clojure 1.11.1-rc1 现已可用 - 请进行测试并给出反馈!
非常感谢,看起来很好!
...