Jackson private constructors,JDK 9+,龙目岛

Jackson private constructors, JDK 9+, Lombok

我正在寻找有关 Jackson 如何在不可变类型上使用私有构造函数的文档。使用 Jackson 2.9.6 和 spring 提供的默认对象映射器启动两个 运行 jdk-10.0.1

给定 JSON:

{"a":"test"} 

并给定一个 class 像:

public class ExampleValue {

    private final String a;

    private ExampleValue() {
        this.a = null;
    }

    public String getA() {
        return this.a;
    }
}

反序列化(令人惊讶的是,至少对我而言)似乎有效。

而这不是:

public class ExampleValue {

    private final String a;

    private ExampleValue(final String  a) {
        this.a = a;
    }

    public String getA() {
        return this.a;
    }
}

这样做:

public class ExampleValue {

    private final String a;

    @java.beans.ConstructorProperties({"a"})
    private ExampleValue(final String a) {
        this.a = a;
    }

    public String getA() {
        return this.a;
    }
}

我的假设是第一个示例可以工作的唯一方法是使用反射来设置最终字段的值(我认为它是由 java.lang.reflect.AccessibleObject.setAccessible(true) 完成的。

问题 1:杰克逊在这种情况下就是这样工作的,我说得对吗?我认为这可能会在不允许此操作的安全管理器下失败?

因此,我个人的偏好是上面的最后一个代码示例,因为它涉及较少 "magic" 并且在安全管理器下工作。但是,我对我发现的关于 Lombok 和构造函数生成的各种线程感到有些困惑,这些线程过去默认生成 @java.beans.ConstructorProperties(...) 但后来更改默认值不再执行此操作,现在允许使用 [= 选择性地配置它17=]

有些人(包括在 lombok release notesv1.16.20)建议:

Oracle more or less broke this annotation with the release of JDK9, necessitating this breaking change.

但我不是很清楚这是什么意思,Oracle 破坏了什么?对于我使用 JDK 10 和 jackson 2.9.6 似乎工作正常。

问题 2:是否有人能够阐明此注释在 JDK9 中是如何被破坏的以及为什么 lombok 现在认为不再默认生成此注释是不可取的。

答案 1: 这正是它的工作原理(同样令我惊讶的是)。根据 Jackson documentation on Mapper Features,属性 INFER_PROPERTY_MUTATORSALLOW_FINAL_FIELDS_AS_MUTATORSCAN_OVERRIDE_ACCESS_MODIFIERS 均默认为 true。因此,在您的第一个示例中,Jackson

  • AccessibleObject#setAccessible (CAN_OVERRIDE_ACCESS_MODIFIERS),
  • 的帮助下使用私有构造函数创建一个实例
  • 检测到(私有)字段的完全可访问的 getter 方法,并认为该字段是可变的 属性 (INFER_PROPERTY_MUTATORS),
  • 由于 ALLOW_FINAL_FIELDS_AS_MUTATORS 而忽略场上的 final,并且
  • 使用 AccessibleObject#setAccessible (CAN_OVERRIDE_ACCESS_MODIFIERS) 获得对该字段的访问权限。

但是,我同意不应该依赖它,因为正如您所说,安全经理可以禁止它,否则 Jackson 的默认值可能会改变。此外,我觉得 "not right",因为我希望 class 是不可变的,并且该字段是不可设置的。

示例 2 不起作用,因为 Jackson 找不到可用的构造函数(因为它无法将字段名称映射到唯一存在的构造函数的参数名称,因为这些名称在运行时不存在)。 @java.beans.ConstructorProperties 在你的第三个例子中绕过了这个问题,因为杰克逊在运行时明确地寻找那个注释。

回答2: 我的解释是 @java.beans.ConstructorProperties 并没有真正损坏,只是不能假定 Java 9+ 不再存在。这是由于它是 java.desktop 模块的成员(例如,请参阅 以了解有关此主题的讨论)。由于模块化 Java 应用程序可能有一个没有此模块的模块路径,如果 lombok 默认生成此注释,则会破坏此类应用程序。 (此外,此注释在 Android SDK 上通常不可用。)

因此,如果您有一个非模块化应用程序或模块路径上带有 java.desktop 的模块化应用程序,那么通过设置 lombok.anyConstructor.addConstructorProperties=true 让 lombok 生成注释或添加如果您不使用 lombok,请手动注释。