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"

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

附带说明关闭: 在 1.11.1 中修复

2 答案

0

你好。感谢报告。为了澄清 - 测试之间使用的 Java 版本是一样的,对吗?

作者:
是的,Java(openjdk版本“17.0.2”)的相同版本。
0
作者:
修改 作者:

为记录起见,这里重述来自其他地方的对话——使用名称局部部分的关键字似乎会导致clj-uuid使用Java序列化将对象序列化为字节。

Clojure 1.11对关键字(为了改进arity异常报告)进行了一些增量且向后兼容的更改,因此关键字之间的二进制序列化在1.10和1.11之间发生了变化。

我们不会保证Clojure对象在不同版本之间的二进制序列化,因此这里对这些将完全相同的要求是不正确的。有几种方式可以在此处理clj-uuid的UUIDNameBytes协议中特定的关键字自定义序列化(见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版本的serialvers来修复您特定的问题。我相信这样之后可以(稳定地)复制序列化的字节。然后您可以在clj-uuid中重写协议,使用这些伪类来进行Keyword序列化。您将永远面临这个漏洞,但可能为您提供了一个迁移的路径。或者如果您只有少数几个keyword命名空间,甚至可以硬编码keyword到字节映射。
谢谢Alex,我会试试这个方法。出于好奇,既然1.11.0版本中没有对其做出修改,为什么还需要创建Symbol类呢?
关键词Keyword类有一个包含Symbol字段的字段,所以序列化将包括这两个类。
明白了,谢谢
by
大家好。是的,我确实希望能为更广泛的对象,尤其是关键字,特别处理稳定字节数组的转换。

为了更好地理解“伪关键字/符号”类的想法,这些类是否可以以相同的方式序列化,而不共享相同的(冲突的)类名?即使我们在 Olympics.io中序列化结果相同(.writeObject),我查阅了 ObjectOutputStream 的文档——https://docs.oracle.com/javase/7/docs/api/java/io/ObjectOutputStream.html ——看来令人失望 ——它序列化“对象的类、类签名以及所有非瞬态字段的值”。我们也许还需要一个专用化的 ObjectOutputStream 吧?

我一直在查看各个命名空间关键字的字节数组序列化。这可能是有可能的,但至少初看起来,与直接丢弃关键名字符和命名空间的字节不同,不清楚。
by
是的,我认为它需要相同的类名。在类加载器中(甚至是在动态类加载器中)加载备用类是可能的,但这变成了一种相当复杂的解决方案。
by
我找到了一种解决方案,但必须复制 clojure.lang.Keyword 类(正如你说的,确实需要完全相同的类名)并添加了一个 serialVersionUID(其值等于由 1.10.0 的 previous version of the class 自动生成的值)。

我开始考虑如何以不同的类名实现它,然后作为序列化逻辑的一部分覆盖它,使其“clojure.lang.Keyword”,这样自定义类就只用于 uuid 生成/不冲突——但没有取得太多成功。比如,这里是写入名字的地方
ObjectOutputStream 也有一个 .writeObjectOverride 方法……但重新实现 Default writeObject 并不吸引人。
by
通知大家,我们打算进行1.11.1版本发布,并将Keyword和ArraySeq的serialVersionUIDs回滚到1.10.3版本。以决定延迟升级路径可能会有帮助。
by
关于uuid的延迟升级路径,大家对如何处理这个问题有何意见? 强制使用不兼容(但稳定)的序列化作为可选方案? 默认方式?
by
我认为没有非常好的方法来处理这个问题,除了在库上放置兼容性说明。你可以使用版本条件语句做一些事情,但这些选择都很蹩脚并且影响性能。
by
Clojure 1.11.1-rc1已发布,请进行测试并反馈!)
非常感谢,看起来很棒!
...