我刚刚看到EBean对记录类文件进行字节码转换的方式让我感觉很奇怪,我想从JVM的Angular 来回答这是否法律的。
显然,可以有一个类文件,其中的类扩展java.lang.Record
并定义记录组件属性(因此它是一个“记录”,就像javac将创建它一样),但它具有javac不允许的以下附加“特性”:
- 将记录组件的字段设置为非最终字段
- 添加既不通过规范构造函数设置,也不通过记录组件属性公开的其他字段
对我来说,这似乎是非法的,我会预料到一个JVM验证错误。我想知道这是否是“支持”的东西,我可以在此基础上构建,或者如果缺少验证是JVM错误。记录仅仅是没有JVM支持的Java语言特性吗?!我读到记录的final字段是“真正final”的,并且可以“即使通过反射也不能改变,并且假设必须有特殊的JVM支持来确保记录与Java语言语义匹配...
4条答案
按热度按时间dtcbnfnu1#
背景
对于一个background to this question相对于ORM建模的串接主键。
9月13日更新
所以有人说:字节码转换器不能删除记录字段的final修饰符。故事到此结束。
至于它的价值,我会加上我们评论中的想法。
正如我们所看到的,这个问题实际上归结为final修饰符和视图记录类型是一个**语言特性(而不是JVM特性)**以及围绕这一特性的含义的问题。
这样,您几乎可以将发布的问题解释为:记录是语言特性还是JVM特性?我们可以将回答的第一部分视为-是的,记录是语言特性(因此需要javac支持jdk,并且需要equals/hashCode等语义)。
所有关于打破记录语义equals/hashCode、访问器、构造器、自定义等的各种问题--这都强化了记录类型确实是一种语言特性的观点。我们非常高兴得到这些[错误的]声明,因为我们可以通过测试证明没有什么被打破,我们可以解释为什么会这样的细节。
问:但是去掉final修饰符有点不靠谱,我们打破了记录,对吧?嗯,它变成了有效final /有效不可变。另一种看待这个问题的方式是扮演魔鬼的拥护者,看看如何填充它--例如,如果我们要创建一个记录示例,部分填充它,然后以部分填充的状态将其传递出去,这将填充equals/hashCode。显然,您不会传递部分加载的记录/部分初始化的示例。我们结束的地方更多的是关于记录类型是否可以从一个语言特性变成一个JVM特性的问题(未来的JVM会假设一个final吗)以及关于这个问题的想法。
需要说明的是,我们没有失败的测试或jvm错误或任何我们可以指出的类似情况-没有将ebean字节码转换器应用于记录而破坏任何东西的情况。我们所拥有的是围绕记录类型是一种语言特性而不是jvm特性这一假设的问题。以及有效final/有效不可变与实际final/不可变问题[一个语义问题,如equals/hashCode与字节码和Java内存模型“适当构造”等]。
9月12日更新
最终,我认为有两种方式来看待这一点:
1.语言视图:记录类型非常重要,它们允许模式匹配,是一把金钥匙,将打开许多很酷的语言功能。细节无关紧要,消息很简单-不要*******!*****
1.详细信息视图:当我们查看字节码,语义,Java内存模型并与如何编写不带记录的浅层不可变类型进行比较时,我们看到完全没有新内容。没有新的字节码,没有不同的语义等。这以ORM
@EmbeddedId
与记录类型完全匹配为典型。同样,ebean需要进行更改以支持完全***无***的记录类型。Brian读到“mutable”和“not final”,然后发射了他的火箭筒,这很公平。问题没有说的是***“effectively immutable”,“effectively final”,“late initialization”***-见鬼,这甚至是Kotlin-
lateinit
中的一个语言特性。一个***字节码转换代理甚至不知道记录类型***,但它却在等待一些选择词。它实际上出错了什么?好吧,一旦你了解了细节--什么都没有。
**问:但是
Record::equals()
的语义是新的吗?**对于字节码转换器不是。****唯一的方法是让dev提供一个自定义的equals/hashCode实现,并且不遵循Record::equals()
的语义-但这是在提供equals/hashCode实现的dev上,而不是在字节码转换器上。还要注意,
Record::equals()
的语义与旧的和现有的@EmbeddedId
匹配。**问:所以ebean什么都不做就支持了java记录?**是的,ebean不需要默认的构造函数,因此我们多年来一直在支持浅层不可变类型。因此记录表现得没有什么新东西。很酷,很有用,但没有什么新东西。
我会把所有的细节都写下来,然后我们会有一个评论,然后从那里开始。
9月11日更新-评审会议
我会为那些对这个问题的细节感兴趣的人组织一个会议。Brian提出的问题。记录类型字节码看起来像什么,增强做了什么,为什么做。当我们处理构造函数、equals/hashCode、访问器、toString等中的自定义代码时,实际上会发生什么(一旦你理解了它的作用,它实际上是非常简单的)。
我很乐意使用
@Embeddable
、record
、junit 5、Java 16进行任何测试。如果在增强下失败了,我会请你喝啤酒!我们可能会请求将测试添加到我们的测试套件(Apache 2)中。Ebean在ORM业务中处理一些有趣的问题,如拦截、延迟加载、部分对象、脏检查等。字节码转换是这个领域中常用的工具,因为它可以极大地简化我们处理一些坚韧问题的方式。记录类型很好,有趣和有用,但他们也不'并且实际上具有与
EmbeddedId
相同的语义和需求。***后续步骤:**召开审查会议并确定如何继续。
9月11日更新
Record::equals()
语义的担忧,这种担忧是不恰当的,没有什么新内容,就字节码转换而言,这里是不同的或困难的。我们现在确实在ebean转换所做的舒适区中。这里出现真正问题的机会显著下降。特定于记录类型的问题的可能性现在几乎为零。为了解释这一点,我们对记录提供的equals/hashCode实现没有问题。如果人们提供定制的equals/hashCode实现,那么这些实现当然必须荣誉记录的语义,但这方面取决于这些实现的作者-就ebean字节码转换而言,它只需要支持一个提供的实现(在拦截方面),但这与非记录普通类的情况没有什么不同。就ebean转换16年来一直在做的事情而言,这里没有什么新的或不同的。摘要
@Entity
和@Embeddable
类)。这些情况是ebean 16年来一直在处理的,我建议这些情况经过了高度的战斗测试。详细信息
作为讨论中的字节码转换的作者,我将添加一些细节。
TLDR:我的意图和期望是,记录的语义(我对这些语义的理解)仍然是100%遵守的。在这个阶段,我需要更清楚地了解Brian不满意的具体语义。
这种字节码转换的有效变化是字节码从“构造时严格的浅层不可变”变为“允许某些后期初始化的有效浅层不可变”。
可能发生的后期初始化对使用转换记录的任何代码/字节码都是透明的--使用转换记录的代码在行为或结果上没有差异,我建议在语义上没有差异。
使用此转换记录的代码仍将认为它是不可变的,并且不能对其进行变化。
对于熟悉Kotlin
lateinit
的人来说,它有点类似于lateinit
--仍然是有效的不可变的,但是允许对所讨论的记录字段进行延迟初始化。[“延迟”的意思是构造之后]还要注意的是,转换添加了一些额外的字段、方法和访问器上的一些拦截,这些都是“仅供内部使用”的,并且没有任何字段或方法被公开地添加到记录中--这些都对使用转换后的记录的代码不可见。我的期望是,它们没有改变记录的语义,但这里需要更清晰。
这些字段删除了final修饰符以允许后期初始化。这意味着从Java内存模型的Angular 来看,我们确实失去了我们在final字段中得到的"final on construction JMM语义“。如果这是特定的问题,我会非常惊讶,但理想的情况是我们得到了澄清。
在再次回顾字节码和阅读上面的注解时,我还不清楚Brian特别不满意的记录的具体语义是什么。在我看来,可能的选择可能是:
同样,所有记录方法(hashcode、equals、toString、constructor)的语义和结果都是不变的,因此最好能清楚地了解特定方的犯规是什么,以及有什么特定的语义是有问题的。
编辑:
快速概览示例
转换前
在转换之后(没有合成字段和方法,IntelliJ反编译为源格式)
字节码差异:
我在第二个答案中以字节码的形式给出了before和after,否则就会超出字符数的限制。
回顾字节码的朋友们,请看第二个贴出的答案。
编辑记录类型的重新自定义
Brian曾建议“但它似乎只适用于不自定义构造函数、访问器、equals或hashCode方法的记录”。
事实并非如此。所有这些都是期望的、允许的、处理的+多个构造函数。为了更好地解释这一点,这些情况与我们多年来一直在处理的非记录类情况没有什么不同。Ebean已经有16年的历史了,我们那时一直在做字节码转换。
问:我们过去犯过错误吗?当然。
Q:我们在
@Embeddable
record
的转换中是否有错误?目前我们还没有错误的证据。(好吧,我们有一个bug,即_ebean_identity字段,但我刚刚修复了它)。虽然记录类型是新的(Java 16),但浅层不可变的概念并不新,字节码形式的记录与我们能够在java中永久编码的不可变类型没有太大区别。
JPA规范需要默认构造函数(顺便说一下:这似乎是一个即将被删除的限制)和getter/setter,但ebean没有这些限制。这意味着Brian提到的定制是ebean转换必须处理很长时间的事情-实际上是16年,因为这些都是非记录实体类所期望的。对于变更实体类的情况,我们需要处理(围绕集合)记录类型不需要处理的其他一些稍微有趣的事情。
也就是说,对于ebean transformer所处理的记录类型,没有任何新的或不同的定制。
另一个要在这里说明的细节是JVM并不总是强制final。从Java 8左右的模糊记忆来看,JVM确实强制了final。这是可能会让Brian担心/烦恼的小细节。
编辑:
当然,我们不应该把事情放在个人身上,但让我们把它放在上下文中。我已经在Java社区工作了25年,我是当地JUG的组织者,我有一个16年前的开源项目,它正在遭受严重的声誉打击。
Brian Geotz,一个Java之神曾经说过“相当严重的聚会犯规”,“被赶出社区”,“无知”,“毒害油井”--对于像我这样的Java粉丝来说,这些都是来自上帝的重击。如果你想知道的话,被称为“作者”实际上并没有减轻这些打击,事实上,它伤害更大,因为它表明这不是一个真正严重的问题。如果它不明显,我真的非常非常非常认真地对待这个问题我将继续详细讨论这个问题,并确认这里的字节码转换是否确实存在问题。
24小时过去了,我坚持住了。我甚至可能愚蠢地认为,我开始触及问题的核心。目前的TLDR可能是,除非你真的知道你在做什么,否则你不应该进行字节码转换。对我来说,我有16年认真对待字节码转换的经验。我不是不知道挑战的大小和知识的深度,你需要得到这个权利。这不是tidly眨眼。
在这个阶段,我们实际上没有错误操作的证据,它更多的是一种暗示,即ebean可能错误地处理了记录类型的定制。这对我来说实际上是 * 真的真的好消息 *,因为我有16年的经验可以依靠,这表明字节码转换确实涵盖了Brian所关心的所有情况(加上Kotlin、Scala和Groovy编译器抛出的其他情况,以及可变类型抛出的其他情况)。
就ebean转换而言,记录类型实际上是一种很简单的情况。
后续步骤:
我们能得到ebean转换做错事的实际证据吗?
Brian也许可以给予我一个简单的例子来测试和报告字节码。我想这就是我们所处的位置。
mf98qq942#
您的问题提出了一个错误的二分法。记录是一种语言特性,具有某种程度的JVM支持(主要是为了反射),但这并不意味着JVM将(甚至可以)强制执行该语言所需的所有记录要求。(像这样的差距是不可避免的,因为JVM是一个更通用的计算基础,它为除Java之外的其他语言服务;例如,JVM允许方法在返回类型上重载,但语言不允许。)
也就是说,你所描述的行为是一个相当严重的聚会犯规,那些参与其中的人应该被赶出社区。(也有可能他们这样做是出于无知,在这种情况下,他们可能是可以教育的。)最有可能的是,这些人认为他们在颠覆他们不喜欢的规则方面是“聪明的”,但在这个过程中,通过促进用户可能会感到惊讶的行为来毒害井。
编辑
Transformer的作者在这里发布了一些关于他们试图完成的内容的进一步的上下文。我将给予他们为符合记录的语义所做的真诚的努力,但是它破坏了final字段的语义,并且似乎只适用于不自定义构造函数、访问器、equals或hashCode方法的记录。这描述了大量的记录,但不是全部。这是一个很好的警示故事;即使在转换类的同时试图保留类的语义,也很容易对类做什么或不做什么做出可疑的假设,这可能会妨碍转换。
作者挥去了对最终字段语义的担忧,认为“不太可能导致问题”。但这不是酒吧。语言为记录提供了某些语义。这种转换破坏了那些语义,但仍然告诉用户它们是记录。即使它们是“次要的”和“不太可能的”,您违反了Java语言承诺的语义。在这种情况下,“99%兼容”舍入为零。因此,我坚持我的Assert,即此框架对语言语义采取了不适当的自由。它们可能已经很好地--出于本意,他们可能努力不打破东西,但打破他们做的事情。
aamkag613#
添加第二个答案时,字节码差异超过了字符限制。
Edit 2:字节码形式的Before和After。
之前
之后
备注:
hec6srdp4#
好吧,我的评论太大了,所以...
更多备注:
因此,该增强功能的设计面向变更实体bean,因此此处的字节码不是严格要求的,或者实际上是
@Embedded
/@EmbeddedId
record
情况下的noop:@DbJson
,在这两种情况下都使用_ebean_intercept。对于记录的情况,我们会失去它吗?也许吧,嗯。"我们是怎么走到这一步的"
好吧,使用ebean,我们已经处理实体和嵌入式bean有final字段和非默认构造函数很多年了。(非常常见)到“完全”不可变(没有setter,看起来与记录非常相似,但是的,这是罕见的),完全不可变的倾向于报告情况(视图、聚合/分组等)。其他ORM,如DataNucleus,也一直在这样做,所以对于一些ORM的人来说,放松字段上的final修饰符并不是一件新鲜事。
从这个意义上说,
record
在字节码方面看起来与我们一直在做的事情没有太大的不同,除了它们附带了hashCode/equals实现,但即使这样,实际上与我们多年来一直在做的事情也没有任何不同- ebean必须荣誉hashCode/equals实现,如果它已经提供的话,等等。在JMM和删除最后修改-我很舒服,我们不是一个坏演员。也许太舒服,但这不是瓦尔哈拉。