Jackson 私有构造函数,JDK 9+,Lombok

Dav*_*vid 5 jackson lombok jackson2 jackson-databind

我正在寻找有关 Jackson 如何与不可变类型的私有构造函数一起工作的文档。使用 Jackson 2.9.6 和 spring boot 提供的默认对象映射器 2 运行 jdk-10.0.1

给定 JSON:

{"a":"test"} 
Run Code Online (Sandbox Code Playgroud)

并给出一个类,如:

public class ExampleValue {

    private final String a;

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

    public String getA() {
        return this.a;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

而这不会:

public class ExampleValue {

    private final String a;

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

    public String getA() {
        return this.a;
    }
}
Run Code Online (Sandbox Code Playgroud)

这样做:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

问题 1:我对杰克逊在这种情况下的工作方式是否正确?我认为这有可能在不允许此操作的安全管理器下失败?

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

有些人(包括在 lombok发行说明v1.16.20)建议:

随着 JDK9 的发布,Oracle 或多或少破坏了这个注释,因此需要进行这种破坏性更改。

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

问题 2:有没有人能够解释一下这个注解在 JDK 9 中是如何被破坏的,以及为什么 lombok 现在认为默认情况下不再生成这个注解是不可取的。

Jan*_*eke 6

答案 1:这正是它的工作原理(也出乎我的意料)。按照上映射杰克逊文档功能,性能INFER_PROPERTY_MUTATORSALLOW_FINAL_FIELDS_AS_MUTATORS以及CAN_OVERRIDE_ACCESS_MODIFIERS所有的默认true。因此,在你的第一个例子中,杰克逊

  • AccessibleObject#setAccessible( CAN_OVERRIDE_ACCESS_MODIFIERS)的帮助下,使用私有构造函数创建一个实例,
  • 检测(私有)字段的完全可访问的 getter 方法,并将该字段视为可变属性 ( INFER_PROPERTY_MUTATORS),
  • 忽略final的领域,由于ALLOW_FINAL_FIELDS_AS_MUTATORS
  • 使用AccessibleObject#setAccessible( CAN_OVERRIDE_ACCESS_MODIFIERS)访问该字段。

但是,我同意人们不应该依赖它,因为正如您所说,安全经理可以禁止它,或者 Jackson 的默认设置可能会改变。此外,我觉得“不对”,因为我希望该类是不可变的,而该字段是不可设置的。

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

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

因此,如果您有非模块化应用程序或java.desktop模块路径上的模块化应用程序,那么让 lombok 通过设置生成注释是完全没问题的lombok.anyConstructor.addConstructorProperties=true,或者如果您不使用 lombok,则手动添加注释。