Java 9 上带有 final 字段的 eclipselink 静态编织

eclipselink static weaving with final fields on Java 9

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

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

当实体插入数据库时​​,这些字段存储在数据库中。它们无法进一步更新。对于 Java 编程语言,此类字段可被视为最终字段。

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

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

在 Java 8 中,使用此类构造构建的程序 运行 很好。但是在Java9中,当涉及到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 规范引起的:

Otherwise, if the field is final, it must be declared in the current class, and the instruction must occur in an instance initialization method () of the current class. Otherwise, an IllegalAccessError is thrown.

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

问题:

编辑:

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

The entity class must not be final. No methods or persistent instance variables of the entity class may be final.

JPA 中是否允许最终字段构造(例如此处介绍的构造)?

如发行说明JDK-8157181中所述,还有一项更改限制编译器接受初始化方法之外的最终字段修改。


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

  1. 如果字段声明在当前class(声明当前方法的class)和
  2. 如果putstatic指令出现在当前class[=62]的classinterface初始化方法<clinit>中=].

否则,必须抛出一个IllegalAccessError

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

  1. 如果字段声明在当前class和
  2. 如果指令出现在当前class的实例初始化方法中。

否则,必须抛出 IllegalAccesError

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

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


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

With the JDK 9 release, the HotSpot VM fully enforces the previously mentioned restrictions, but only for class files with version number >= 53(Java 9). For class files with version numbers < 53, restrictions are only partially enforced (as it is done by releases preceding JDK 9). That is, for class files with version number < 53 final fields can be modified in any method of the class declaring the field (not only class/instance initializers).

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

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

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

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

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

Eclipselink 不承认,但是,我的项目需要这个。所以我做了一个肮脏的 "hack" 才能做到。基本上,我已经覆盖了 eclipselink class 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 应该考虑添加这个,至少作为一个选项。