Chr*_*kov 9 java jvm bytecode jvm-bytecode java-record
我刚刚看到 EBean 以一种让我感觉奇怪的方式对记录类文件进行字节码转换,我寻求从 JVM 角度来看这是否合法的答案。
显然,可以有一个类文件,其中类扩展java.lang.Record并定义记录组件属性(因此它是一个“记录”,就像 javac 会创建它一样),但具有 javac 不允许的以下附加“功能”:
对我来说,这似乎是非法的,我预计会出现 JVM 验证错误。我想知道这是否是“受支持”的东西,我可以在此基础上进行构建,或者缺乏验证是否是 JVM 错误。难道记录只是Java语言的一个特性,没有JVM的支持吗?!我读到记录的最终字段是“真正的最终”,即使通过反射也无法更改,并且假设必须有特殊的 JVM 支持来确保记录与 Java 语言语义相匹配......
Rob*_*ave 14
有关串联主键的 ORM 建模的这个问题的背景。
所以有人指出:字节码转换器无法删除记录字段的最终修饰符。故事就这样结束了。
就其价值而言,我将添加我们评论中得出的想法。
正如我们所看到的,这个问题实际上归结为最终修饰符,视图记录类型是一种语言功能(而不是 jvm 功能)以及围绕其含义的问题。
这样,您几乎可以将发布的问题解释为:记录是语言功能还是 jvm 功能?我们可以将响应的第一部分视为 - 是的,记录是一种语言功能(因此对具有 jdk 支持的 javac 的要求以及 equals/hashCode 等的语义要求)。
所有围绕打破记录语义 equals/hashCode、访问器、构造函数、自定义等的各种问题 - 这一切都强化了记录类型确实是一种语言功能的观点。我们非常高兴收到这些[虚假]声明,因为我们可以通过测试证明没有任何问题,并且我们可以详细解释原因。
问:但是删除最终修饰符是很狡猾的,我们打破了记录,对吗?好吧,它实际上是最终的/实际上是不可变的。另一种看待这个问题的方法是扮演魔鬼的拥护者,看看如何填充它 - 例如,如果我们要创建一个记录实例,部分填充它并以部分填充的状态将其移交,这将填充 equals/hashCode。显然,您不会移交部分加载的记录/部分初始化的实例。我们结束的地方更多是关于记录类型是否可以从语言功能转变为 jvm 功能(未来的 jvm 会采用最终的功能)的问题以及围绕该问题的想法。
需要明确的是,我们没有失败的测试或 jvm 错误或任何类似的东西,我们可以指出 - 不存在将 ebean 字节码转换器应用于记录并破坏任何内容的情况。我们所面临的问题是关于记录类型是语言功能与 jvm 功能这一假设的问题,以及有效最终/有效不可变与实际最终/不可变的问题[像 equals/hashCode 与字节码和 Java 内存模型这样的语义问题“适当的施工”等]。
最终我认为有两种方式来看待这个问题:
语言视图:记录类型非常重要,它们允许模式匹配成为一把金钥匙,可以解锁许多很酷的语言功能。细节并不重要,信息很简单——没有人会搞砸!
详细信息视图:当我们查看字节码、语义、Java 内存模型并将其与编写没有记录的浅层不可变类型进行比较时,我们并没有看到任何新内容。没有新的字节码,没有不同的语义等。这以 ORM@EmbeddedId与记录类型的精确匹配为代表。类似地,ebean 需要进行更改以支持记录类型,但完全没有。
布莱恩读到“可变”和“不是最终”并发射了他的火箭筒,这很公平。问题没有说的是“有效地不可变”,“有效地最终”,“后期初始化” - 哎呀,它甚至是 kotlin 中的一个语言功能 - lateinit。
甚至不知道记录类型的字节码转换代理会针对某些选择字进行排列。它实际上出了什么问题?好吧,一旦你了解细节——什么也没有。
问:但是 的语义Record::equals()是新的?不是字节码转换器。解决这个问题的唯一方法是开发人员提供自定义的 equals/hashCode 实现,并且不遵循以下语义Record::equals()- 但这是在提供 equals/hashCode 实现的开发人员上,而不是在字节码转换器上。
Record::equals()还要注意匹配旧的和现有的语义@EmbeddedId。从 ORM 的角度来看,这实际上并不新鲜。
问:那么 ebean 通过什么都不做来支持 java 记录? 是的,ebean 不需要默认构造函数,因此我们多年来一直支持浅层不可变类型。因此,记录看起来并不新鲜。很酷而且很有用,但没什么新鲜的。
我将写下所有详细信息,我们将进行审查并从那里开始。
我将寻求为对这个问题的细节感兴趣的人们组织一次会议。布莱恩提出的问题。记录类型字节码是什么样的,增强功能有什么作用以及为什么这样做。当我们处理构造函数、equals/hashCode、访问器、toString 等中的自定义代码时,实际会发生什么(一旦你了解了它的作用,它实际上非常简单)。
我很乐意使用@Embeddable、recordjunit5、Java 16 进行任何测试。如果在增强功能下失败,我会给你买瓶啤酒!我们可能会请求许可将测试添加到我们的测试套件(Apache2)中。
Ebean 在 ORM 业务中处理一些有趣的问题,如拦截、延迟加载、部分对象、脏检查等。字节码转换是该领域常用的工具,因为它可以极大地简化我们处理一些棘手问题的方式。记录类型很好、有趣且有用,但它们也没有为 ebean 字节码转换提供任何新内容,并且实际上具有与EmbeddedId.
后续步骤:召开审查会议并确定如何继续。
Record::equals(). 这种担心是错误的,就字节码转换而言,这里没有什么新的、不同的或困难的。我们现在确实处于 ebean 转型的舒适区。这里出现真正问题的可能性已经大大下降。特定于记录类型的问题的可能性现在几乎为零。为了解释这一点,我们对提供的记录 equals/hashCode 实现没有任何问题。如果人们提供定制的 equals/hashCode 实现,那么这些当然必须遵守记录的语义,但这方面取决于这些实现的作者 - 就 ebean 字节码转换而言,它只需要支持提供的实现(在拦截方面)但这与无记录的普通班级情况没有什么不同。就 ebean 转型 16 年来所做的事情而言,这里并没有什么新的或不同的地方。@Entity和@Embeddable类)中处理的情况。这些案例是 ebean 16 年来一直在处理的案例,我建议它们经过了严格的实战检验。作为相关字节码转换的作者,我将添加一些细节。
TLDR:我的意图和期望是记录的语义(据我理解这些语义)仍然 100% 得到尊重。在这个阶段,我需要更清楚地了解 Brian 不满意的具体语义。
这种字节码转换的有效变化是字节码从“构造时严格浅不可变”变为“允许后期初始化的有效浅不可变”。
可能发生的后期初始化对于使用转换后的记录的任何代码/字节码都是透明的 - 使用转换后的记录的代码在行为或结果上不会有任何差异,并且我建议语义上没有差异。
使用此转换后的记录的代码仍然会认为它是不可变的并且无法改变它。
对于熟悉 Kotlin 的人来说,lateinit它有点类似 - 仍然有效地不可变,但允许稍后初始化相关记录字段。[施工后具有‘迟到’的意思]
还要注意的是,转换会在访问器上添加一些额外的字段、方法和一些拦截,所有这些都“仅供内部使用”,并且不会在字段或方法方面公开添加任何内容 - 这些对于使用转换后的代码来说都是不可见的记录。我的期望是他们没有改变记录的语义,但这里需要更加清晰。
这些字段已删除最终修饰符以允许后期初始化。这意味着从 Java 内存模型的角度来看,我们确实失去了通常使用 Final 字段获得的良好的“构造 JMM 语义的 Final”。如果这是具体问题,我会感到非常惊讶,但理想情况下我们能澄清这一点。
在再次查看字节码并阅读上面的评论时,我还不清楚布莱恩对记录的具体语义特别不满意。据我所知,可能的选择可能是:
同样,所有记录方法(hashcode、equals、toString、构造函数)的语义和结果都保持不变,因此最好清楚地了解具体的一方犯规是什么,以及有问题的具体语义。
编辑:
快速概览示例
改造前
@Embeddable
public record UserRoleId(Integer userId, String roleId) {}
Run Code Online (Sandbox Code Playgroud)
转换后(没有合成字段和方法,IntelliJ 反编译为源形式)
@Embeddable
public record UserRoleId(Integer userId, String roleId) implements EntityBean {
private Integer userId;
private String roleId;
public UserRoleId(Integer userId, String roleId) {
this._ebean_intercept = new InterceptReadWrite(this);
this._ebean_set_userId(userId);
this._ebean_set_roleId(roleId);
}
public Integer userId() {
return this._ebean_get_userId();
}
public String roleId() {
return this._ebean_get_roleId();
}
}
Run Code Online (Sandbox Code Playgroud)
字节码差异:
我已将字节码形式的之前和之后放在第二个答案中,否则我们会超出此处的字符限制。
查看字节码的人们,请查看第二个发布的答案。
编辑记录类型的重新定制
Brian 建议“但它似乎只适用于不自定义构造函数、访问器、equals 或 hashCode 方法的记录”。
事实并非如此。所有这些的定制是期望的、允许的、处理的+多个构造函数。进一步解释一下,这些案件与我们多年来已经处理的非记录级案件没有什么不同。Ebean已经16岁了,那时候我们一直在做字节码转换。
问:我们过去有犯过错误吗?绝对地。
问:我们在转型方面是否存在错误@Embeddable record?目前我们没有证据表明存在错误。(好吧,这是一个错误,我们在那里有 _ebean_identity 字段,但我刚刚修复了它)。
尽管记录类型是新的(Java 16),但浅不可变性的概念并不新鲜,并且字节码明智的记录与我们已经能够在 java 中永远编码的不可变类型没有太大区别。
JPA 规范需要默认构造函数(顺便说一句:似乎很快就会删除限制)和 getters/setters,但 ebean 没有这些限制。这意味着 Brian 提到的定制是 ebean 转换必须处理很长时间的事情 - 实际上 16 年,因为这些都是非记录实体类所期望的事情。对于变异实体类的情况,我们需要处理其他一些有趣的事情(围绕集合),而对于记录类型,我们不需要处理这些事情。
也就是说,没有任何记录类型的自定义是新的或与 ebean 转换器处理的内容不同的。
这里需要说明的另一个细节是 JVM 并不总是强制执行final。从 Java 8 左右的模糊记忆来看,JVM 确实强制执行了final。这是布莱恩可能关心/困扰的小细节。
编辑:
我们绝对不应该把事情看成是针对个人的,而是让我们把它放在上下文中。我在 Java 社区工作了 25 年,我是当地 JUG 的组织者,我有一个已有 16 年历史的开源项目,但在这里却遭受了严重的声誉打击。
Brian Geotz,一位名副其实的 Java 之神,说过“相当严重的党派犯规”、“被社区羞辱”、“无知”、“毒害井”——对于像我这样的 Java 迷来说,这些简直就是来自一个 Java 迷的重击。上帝。如果你想知道的话,被称为“作者”实际上并不会减轻这些打击,事实上它更伤人,因为它表明这不是一个真正严重的问题。如果不是很明显,我会非常非常认真地对待这个问题,我将努力了解这个细节,并确认这里的字节码转换是否确实存在问题。
24小时过去了,我还在坚持。我什至可能愚蠢地认为我已经开始触及问题的核心了。当前的 TLDR 可能是,除非您真正知道自己在做什么,否则不应该进行字节码转换。就我自己而言,我已经认真对待字节码转换 16 年了。我并非不了解挑战的规模以及做好这项工作所需的知识深度。这可不是眨眼。
在这个阶段,我们实际上没有错误行为的证据,这更多的是表明 ebean 可能错误地处理记录类型的自定义。这对我来说实际上是一个非常好的消息,因为我有 16 年的经验可以依靠,这表明字节码转换确实涵盖了 Brian 关心的所有情况(加上 Kotlin、Scala 和 Groovy 提出的其他情况)编译器和可变类型引发的其他情况)。
就 ebean 转换而言,记录类型实际上是非常简单的情况。
下一步:
我们能否获得 ebean 转型做错事的实际证据?
Brian 也许可以给我一个简单的例子来测试并报告字节码。我想这就是我们现在的处境。
你的问题提出了错误的二分法。记录是一种语言功能,具有一定程度的 JVM 支持(主要用于反射),但这并不意味着 JVM 将(甚至可以)强制执行该语言所需的记录的所有要求。(这样的差距是不可避免的,因为 JVM 是更通用的计算基础,并且还为 Java 之外的其他语言提供服务;例如,JVM 允许在返回类型上重载方法,但该语言不允许。)
也就是说,你所描述的行为是相当严重的党内犯规,那些参与其中的人应该被羞辱出社区。(也有可能他们这样做是出于无知,在这种情况下,他们可能是可以接受教育的。)最有可能的是,这些人认为他们“聪明”地颠覆了他们不喜欢的规则,但在这个过程中,毒害了规则通过推广用户可能会感到惊讶的行为。
编辑
变压器的作者在这里发布了一些关于他们试图实现的目标的进一步背景信息。我会赞扬他们为符合记录语义而做出的真诚努力,但它破坏了最终的字段语义,并且似乎只适用于不自定义构造函数、访问器、equals 或 hashCode 方法的记录。这描述了很多记录,但不是全部。这是一个很好的警示故事;即使在转换类时试图保留类的语义时,也很容易对类做什么或不做什么做出有问题的假设,从而造成妨碍。
作者消除了对最终字段语义的担忧,认为“不太可能引起问题”。但这不是酒吧。该语言为记录提供了某些语义。这种转换破坏了这些语义,但仍然告诉用户它们是记录。即使它们是“次要的”和“不太可能的”,您也违反了 Java 语言所承诺的语义。在这种情况下,“99% 兼容”四舍五入为零。因此,我坚持我的观点,即该框架对语言语义采取了不适当的自由。他们可能是出于好意,他们可能努力不去破坏东西,但却破坏了他们所做的事情。
| 归档时间: |
|
| 查看次数: |
1028 次 |
| 最近记录: |