【问题标题】:eclipselink static weaving with final fields on Java 9Java 9 上带有最终字段的 eclipselink 静态编织
【发布时间】:2018-03-04 16:50:37
【问题描述】:

我有一些像这样声明为 final 的 JPA 注释字段:

@Column(name = "SOME_FIELD", updatable = false, nullable = false)
private final String someField;

当实体插入数据库时​​,这些字段存储在数据库中。它们无法进一步更新。对于 Java 编程语言,这样的字段可以被认为是 final 的。

使用 EclipseLink 静态编织插件,由于某些字节码检测,可以延迟加载实体。

我不知道 JPA 中是否正式允许此类构造(最终字段),但我喜欢使用它们,因为它们在编译时强制这些字段不打算更新。

在 Java 8 中,使用这种结构构建的程序运行良好。但是在 Java 9 中,当涉及到 EclipseLink 静态编织时,我得到以下运行时异常:

Caused by: java.lang.IllegalAccessError: Update to non-static final field xxx.yyy.SomeEntity.someField attempted from a different method (_persistence_set) than the initializer method <init> 

这样的错误似乎是由于以下JVM规范:

否则,如果该字段是final,则必须在当前声明 类,并且指令必须发生在实例初始化中 当前类的方法()。否则,一个 IllegalAccessError 被抛出。

因此,我觉得一些编织工具需要一些更新来满足这个 JVM 规范。 EclipseLink 静态编织工具似乎就是其中之一。

问题

  • JPA 中是否允许使用此处介绍的 final 字段构造?
  • 是否有一个 JDK 9 选项来禁用对其他地方的最终字段分配的检查,而不是仅在类的实例初始化方法()中(作为临时解决方法)?

编辑

根据 JPA 规范,JPA 中不允许使用最终字段:

实体类不能是最终的。实体类的任何方法或持久实例变量都不能是最终的。

【问题讨论】:

  • 您能说是否可以在 java 9 上使用 EclipseLink(作为自动模块)?如果是,您使用的是什么版本的 EclipseLink?

标签: jpa eclipselink java-9 compile-time-weaving


【解决方案1】:

JPA 中是否允许使用此处介绍的 final 字段结构?

正如发布说明 JDK-8157181 中所提到的,还有一个更改是为了限制编译器接受对初始化方法之外的最终字段的修改。


根据 Java VM 规范,putstatic 字节码只允许修改 final 字段

  1. 如果该字段在当前类(声明当前方法的类)中声明并且
  2. 如果putstatic指令出现在当前类classinterfaceinitializer方法&lt;clinit&gt;中。

否则,必须抛出 IllegalAccessError

同样,putfield 字节码只允许修改final 字段

  1. 如果在当前类中声明了该字段并且
  2. 如果指令出现在当前类的实例初始化方法中。

否则,IllegalAccesError 必须被抛出。

不满足条件 (2) 的方法违反了编译器的假设。 并且一直在检查以使用 Java 9 实现所需条件。

你可以关注BugReport上面的详细解释。


是否有一个 JDK 9 选项可以在其他地方禁用对最终字段分配的检查,而不是仅在类的实例初始化方法()中(作为临时解决方法)?

在 JDK 9 版本中,HotSpot VM 完全强制执行以前的 提到的限制,但仅适用于 版本号 >= 53(Java 9) 的类文件。对于版本号 版本号 final 字段可以在类的任何方法中修改 声明字段(不仅是类/实例初始化器)

因此,您可以尝试使用源代码和目标 1.8 编译您的代码,以检查现在是否可以解决问题。这应该可以通过--release 8 选项使用最新的javac tool 来实现。

【讨论】:

  • 发布说明@nullpointer 引用可能确实是问题所在,在这种情况下编译为--release 8 可能是解决方法。有人应该检查 EclipseLink 错误数据库,也许它已经被修复了(JDK 9 的 EA 版本自 2016 年年中以来就已提供此更改)。
  • 在编译实现应用程序持久性部分的项目时,我已将发布目标更改为 8,并且工作正常。谢谢。我将检查 EclipseLink 错误数据库。
【解决方案2】:

请阅读 JPA 规范 - http://download.oracle.com/otndocs/jcp/persistence-2_2-mrel-eval-spec/index.html - 第 2.1 节实体类:

实体类不能是最终的。实体类的任何方法或持久实例变量都不能是最终的。

方法_persistence_set是通过编织(实体类的字节码操作)添加的代码,用于在默认构造函数(没有属性)调用创建类后初始化实体属性的值。 Java 1.8 允许使用 final 字段,但 Java 9 不允许。

您现在应该遵循 JPA 规范,并且不应将最终持久性属性放入您的实体中。

【讨论】:

  • 在eclipselink中有一个解决方法可以跳过这个限制,看我的回答
【解决方案3】:

Eclipselink 不承认,但是,我希望在我的项目中使用它。所以我做了一个肮脏的“黑客”来做到这一点。基本上,我已经覆盖了 eclipselink 类 org.eclipse.persistence.internal.jpa.weaving.ClassWeaver 并添加了这个方法:

        @Override
public FieldVisitor visitField(
        final int access,
        final String name,
        final String descriptor,
        final String signature,
        final Object value) {
    if (cv != null) {
        int newAccess = access;
        if (!Modifier.isStatic(access) && Modifier.isFinal(access)) {
            newAccess = access & (~Opcodes.ACC_FINAL);
        }
        return cv.visitField(newAccess, name, descriptor, signature, value);
    }
    return null;
}

这将在编织时从实体上的非静态字段中删除所有最终修饰符。

我认为 eclipselink 应该考虑添加这个,至少作为一个选项。

【讨论】:

  • 恕我直言,值对象(如 DDD 中所定义)应具有声明为 final 的字段。但是,当它们使用 Embeddable 注释进行注释时,由于 JPA(Java 规范?)约束,我们必须删除 final 修饰符。我很想知道当 Java 原生支持值对象时 Java 规范会提出什么建议。
猜你喜欢
  • 2013-08-31
  • 2011-10-25
  • 2012-05-30
  • 1970-01-01
  • 1970-01-01
  • 2012-09-29
  • 2015-04-13
  • 1970-01-01
  • 2012-04-20
相关资源
最近更新 更多