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

Posted

技术标签:

【中文标题】Jackson 私有构造函数,JDK 9+,Lombok【英文标题】:Jackson private constructors, JDK 9+, Lombok 【发布时间】:2019-01-03 22:40:30 【问题描述】:

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

给定 JSON:

"a":"test" 

并给定一个类似的类:

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:这就是杰克逊在这种情况下的工作方式,我说得对吗?我认为这在不允许此操作的安全管理器下可能会失败?

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

有些人(包括在龙目岛release notes 为v1.16.20)建议:

随着 JDK9 的发布,Oracle 或多或少地破坏了这个注解,因此需要这个突破性的改变。

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

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

【问题讨论】:

【参考方案1】:

答案 1: 这正是它的工作原理(也令我惊讶)。根据Jackson documentation on Mapper Features,属性INFER_PROPERTY_MUTATORSALLOW_FINAL_FIELDS_AS_MUTATORSCAN_OVERRIDE_ACCESS_MODIFIERS都默认为true。因此,在你的第一个例子中,杰克逊

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

但是,我同意不应依赖这一点,因为正如您所说,安全经理可能会禁止它,或者杰克逊的默认设置可能会改变。此外,我觉得它“不正确”,因为我希望该类是不可变的并且该字段是不可设置的。

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

答案 2: 我的解释是 @java.beans.ConstructorProperties 并没有真正损坏,但不能假设在 Java 9+ 中不再存在。这是由于它在 java.desktop 模块中的成员身份(参见,例如,this thread 以了解有关此主题的讨论)。由于模块化的 Java 应用程序可能有一个没有此模块的模块路径,如果 lombok 默认生成此注解,它将破坏此类应用程序。 (此外,android SDK 上一般不提供此注解。)

因此,如果您有一个非模块化应用程序或模块化应用程序在模块路径上带有java.desktop,则可以通过设置lombok.anyConstructor.addConstructorProperties=true 让lombok 生成注释,或者如果您不是手动添加注释使用龙目岛。

【讨论】:

以上是关于Jackson 私有构造函数,JDK 9+,Lombok的主要内容,如果未能解决你的问题,请参考以下文章

在构造函数python中使用私有方法

Jackson@JsonCreator 注解

spring5.0.7.RELEASE配置jackson2.9.5

Jackson 啥时候需要无参数构造函数进行反序列化?

Jackson 可以自动将任何构造函数参数视为 JsonProperty 吗?

使用 Jackson 反序列化 JSON - 为啥 JsonMappingException“没有合适的构造函数”?