升级到 Spring Boot 2 后,如果没有默认构造函数,ObjectMapper 无法反序列化

ObjectMapper can't deserialize without default constructor after upgrade to Spring Boot 2

我有以下 DTO:

@Value
public class PracticeResults {
    @NotNull
    Map<Long, Boolean> wordAnswers;
}

@Value
public class ProfileMetaDto {

    @NotEmpty
    String name;
    @Email
    String email;
    @Size(min = 5)
    String password;
}

@Value 是生成构造函数的 Lombok 注释。这意味着此 class 没有无参数构造函数。

我使用 Spring Boot 1.4.3.RELEASE 并且 ObjectMapper bean 能够从 JSON.

中反序列化此类对象

升级到 Spring Boot 2.0.0.M7 后,我收到以下异常:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of PracticeResults (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

Spring Boot 1.4.3 中使用的 Jackson 版本是 2.8.10 并且 Spring Boot 2.0.0.M7 是 2.9.2

我已经尝试 Google 这个问题,但只找到 @JsonCreator@JsonProperty 的解决方案。

那么,为什么它与 Spring Boot 1.4.3 一起工作而与 Spring Boot 2 一起失败?是否可以将 bean 配置为与旧版本的行为方式相同?

由于 Lombok 版本 1.16.20 中的重大更改,您需要在 lombok.config 文件中设置以下 属性(如果您没有此文件,则可以在项目中创建它根):

lombok.anyConstructor.addConstructorProperties=true

这在 Lombok 更新日志中有描述:https://projectlombok.org/changelog

之后 @Value 应该再次被 Jackson 接受。

您可能有兴趣关注此处的相关 GitHub 问题,尽管它是关于 @Data 的:https://github.com/rzwitserloot/lombok/issues/1563

我已将 lombok 版本升级到:'org.projectlombok:lombok:1.18.0',它对我有用。

解决这个问题的另一种方法。使用 Jackson parameter names module,它默认包含在 spring boot 2 中。在此之后,杰克逊可以反序列化对象。但它只有在对象中有超过 1 个 属性 时才有效。在单个 属性 的情况下,我收到以下错误消息:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `SomeClassName` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

由于以下原因:

Marker annotation that can be used to define constructors and factory methods as one to use for instantiating new instances of the associated class.

NOTE: when annotating creator methods (constructors, factory methods), method must either be:

  • Single-argument constructor/factory method without JsonProperty annotation for the argument: if so, this is so-called "delegate creator", in which case Jackson first binds JSON into type of the argument, and then calls creator. This is often used in conjunction with JsonValue (used for serialization).
  • Constructor/factory method where every argument is annotated with either JsonProperty or JacksonInject, to indicate name of property to bind to

Also note that all JsonProperty annotations must specify actual name (NOT empty String for "default") unless you use one of extension modules that can detect parameter name; this because default JDK versions before 8 have not been able to store and/or retrieve parameter names from bytecode. But with JDK 8 (or using helper libraries such as Paranamer, or other JVM languages like Scala or Kotlin), specifying name is optional.

为了使用 Lombok 处理这种情况,我使用了以下解决方法:

@Value
@AllArgsConstructor(onConstructor = @__(@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)))
class SomeClassName {...}

我遇到了这个问题和对我有用的解决方案是创建一个 没有字段的默认构造函数 并且问题消失了。

如果您的 class 中有一些最终字段 并且您希望对象创建者使用特定的构造函数要将 POJO 与 @Data 注释一起反序列化,我建议使用 @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) 注释构造函数,并使用 @JsonProperty("<field_name>") 注释具体的必需构造函数参数。示例如下:

package test.model;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

import java.util.Date;

@Data
public class KafkaEventPojo {
    private final String executionId;
    private final String folder;
    private final Date created_at;

    private Date updated_at;
    // ... other params
    
    /**
     * Instantiates a new Kafka event pojo.
     *
     * @param executionId the execution or flow id of the event lifecycle
     * @param folder      the folder to be retrieved
     */
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public KafkaEventPojo(@JsonProperty("executionId") String executionId, @JsonProperty("folder") String folder) {
        this(executionId, folder, new Date(System.currentTimeMillis()));
    }

    private KafkaEventPojo(String executionId, String folder, Date date) {
        this.executionId = executionId;
        this.folder = folder;

        this.created_at = date;
        this.updated_at = date;
    }
    
    // ... other logic
}

正如其他人所说,您需要添加更多 @Value 注释来告诉 Lombok 在生成的构造函数

上写入 @JsonCreator
@AllArgsConstructor(onConstructor_={@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)})

注意 如果您使用的是 JDK8+(这在 javadoc 中提到),这里的语法与其他答案略有不同

如果您使用的是免费 gradle 插件,您还需要配置它以在 build.gradle:

中添加构造函数属性
lombok {
    config['lombok.anyConstructor.addConstructorProperties'] = 'true'
}

我遇到了这个问题,但我的解决方案不涉及添加:(mode = JsonCreator.Mode.PROPERTIES)

真正解决我问题的是构造函数中的@JsonProperty

我的最终结果是这样的:

@Value
public class ServerTimeResponse {

    long serverTime;

    @JsonCreator
    public ServerTimeResponse(@JsonProperty("serverTime") long serverTime) {
        this.serverTime = serverTime;
    }

}

如果您 运行 在本机模式下出现错误是因为反射解决了用 @RegisterForReflection 注释您的 class 并重建应用程序和 运行 你的错误将得到解决

link

上进行信息搜索