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

欢迎!请参阅 关于 页面以获取更多有关此如何运作的信息。

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

(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"

经过一些试错,似乎是由于 local part 参数(即::some-keyword)导致差异,因为在 clj-uuid/v5 函数中,它将 keyword Object 转换为 ByteArray,而现在看来它与之前不同。(如果我使用 String 而不是 keyword 作为 local part 参数,那么产生的 uuid 在 Clojure 升级前后都是一致的。)
产生的 uuid 用在下游系统中,并且处理这种变化相当困难。有没有什么办法可以产生与之前完全相同的 uuid 呢?

以以下备注结束: 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类(相同的非transient字段)并在其中设置serialVersionUID为1.10期间的serialvers,可能可以修复您的特定问题。我相信您(稳定地)可以复制序列化的字节数据。然后您可以在clj-uuid中重写协议以使用这些类进行Keyword序列化。您将永远被困在这个漏洞中,但这可能至少给您一个迁移的途径。或者,如果您有少量keyword命名空间,甚至可以硬编码keyword到字节的映射。
谢谢Alex,我会试试看。出于好奇,如果1.11.0中没有对其进行过更改,为什么要创建一个Symbol类呢?
Keyword类有一个字段用于存放Symbol,因此序列化将包括这两个类。
明白了,谢谢
大家好。是的,我确实很希望能够为更广泛的对象,尤其是关键字提供特别的字节序列化函数。

为了更好地理解“伪关键字/符号”类的设计,这些类是否可以在不共享相同的(冲突的)类名的情况下以相同的方式进行序列化?即使我们以相同的方式序列化(.writeObject),我对ObjectOutputStream的阅读(https://docs.oracle.com/javase/7/docs/api/java/io/ObjectOutputStream.html)看起来很令人失望——它序列化“对象的类、类签名以及所有非transient字段的值”。我想我们还需要专门的ObjectOutputStream吗?

我一直在查看各个命名空间关键字的字节序列化。这可能是有可能的,但至少从第一眼看来,它并不像直接投放关键字的名称和命名空间的字节那样清晰。
by
是的,我认为它需要相同的类名。在classloader中加载不同的类是可能的(即使是动态classloader),但这已经成为一个相当复杂的解决方案了。
by
我让这工作了,但是必须复制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
我在这里报告了类似的问题:https://ask.clojure.org/index.php/11699/deserializing-upgrading-clojure-invalidclassexception
按此通知,我认为我们将进行1.11.1版本的更新,并将Keyword和ArraySeq的serialVersionUIDs重新锁定到1.10.3版本。以防这有助于决定延迟升级路径。
关于uuid的延迟升级路径,对此有什么看法?强制不兼容(但稳定)的序列化为可选?默认吗?
我认为除了在库中放置兼容性说明外,没有其他好的处理方式。你可以使用版本条件语句做一些事情,但这些选项都非常糟糕,会影响性能。
Clojure 1.11.1-rc1现已可用 - 请进行测试并反馈!
非常感谢,看起来很好!
...