【问题标题】:REST How to implement polymorphic POST end point: abstract types need to be mapped to concrete typesREST 如何实现多态 POST 端点:抽象类型需要映射到具体类型
【发布时间】:2019-08-23 12:57:00
【问题描述】:

我正在开发一个 SpringBoot 应用程序。

我有以下类层次结构:

public abstract class DlqMessage {
    Long id;
    UUID employeeId;
    EventReason reason;
}

public class ContractUpdateMessage extends DlqMessage {}

public class SingleContractUpdateMessage extends DlqMessage {
    UUID benefitId;
    UUID employerId;
}

因此,ContractUpdateMessageSingleContractUpdateMessage 类只有几个字段不同。

我有一个 REST 控制器,它使用 POST 在数据库中创建和保存新实体:

@PostMapping("/messages")
public ResponseEntity<DlqMessage> create(@RequestBody DlqMessage contractUpdateMessage) {
    DlqMesssage dlqEntry = dlqEntityService.save(contractUpdateMessage);
    return ResponseEntity.ok(dlqEntry);
}

现在,我有一个在一个或另一个类的实例上随机生成的测试:

 DlqMessage message = randomOneOf(contractUpdateMessageBuilder().build(), singleContractUpdateMessageBuilder().build());

然后我有一个测试助手类,它使用 RestTemplate 向控制器发送 POST 请求:

ResponseEntity<DlqMesssage> response =
                crudRestClient.post("/messages/contract", message, DlqMesssage.class, null);
        return response.getBody();

并调用整个事情,我最终得到以下例外:

Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Can not construct instance of com.orbitbenefits.benefitsselection.server.errorrecovery.entity.DlqMessage: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.orbitbenefits.benefitsselection.server.errorrecovery.entity.DlqMessage: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information

看起来我的请求甚至没有被 RestTemplate 发送。

顺便说一句,当我为每个单独的子类型拆分端点时,这一切都有效:

@PostMapping("/messages/contract")
public ResponseEntity<DlqMessage> create(@RequestBody ContractUpdateMessage contractUpdateMessage) {
    ContractUpdateMessage dlqEntry = (ContractUpdateMessage) dlqEntityService.save(contractUpdateMessage);
    return ResponseEntity.ok(dlqEntry);
}

@PostMapping("/messages/single")
public ResponseEntity<DlqMessage> create(@RequestBody SingleContractUpdateMessage contractUpdateMessage) {
    SingleContractUpdateMessage dlqEntry = (SingleContractUpdateMessage) dlqEntityService.save(contractUpdateMessage);
    return ResponseEntity.ok(dlqEntry);
}

然而,这看起来很难看,并不是一个“正确”的解决方案。

基本上,我想知道它是否可能以及如何实现以多态实例作为参数的REST端点以及如何调用这样的端点?

【问题讨论】:

    标签: java spring rest


    【解决方案1】:

    你不能使用抽象类型 DlqMessage 的原因是你可能会收到这样的消息:

    {
      "id":300, 
      "employeeId":"d6a00fb2-058c-4bf7-9a0a-7cc538cd85f5"
    }
    

    Jackson 无法确定此消息打算映射的具体对象的类型。处理这个问题的最简单方法是定义类型提示,例如:

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "messageType")
    @JsonSubTypes({
        @JsonSubTypes.Type(value=ContractUpdateMessage.class, name = "ContractUpdateMessage"),
        @JsonSubTypes.Type(value=SingleContractUpdateMessage.class, name = "SingleContractUpdateMessage")
    })
    public abstract class DlqMessage { ... }
    

    这样,下次您进行 API 调用时,您还必须在 JSON 对象中包含此类型提示:

    {
      "id":300, 
      "employeeId":"d6a00fb2-058c-4bf7-9a0a-7cc538cd85f5",
      "messageType":"ContractUpdateMessage"
    }
    

    这样,Jackson 的默认对象映射器将使用字段“messageType”来猜测您的 API 正在接收哪种类型的 DlqMessage。

    编辑:您可以在此处找到更多信息:

    https://www.baeldung.com/jackson-annotations

    【讨论】:

      【解决方案2】:

      问题在于 spring 正在尝试直接从输入有效负载创建 DlqMessage 的实例。假设您的 API 接受 JSON 输入,您需要指示 json 解析器如何根据有效负载内容区分子类型。

      这是一个类似问题的链接,该链接显示了如何使用 jackson json 执行此操作的示例。看起来您必须通过@JsonSubTypes 使用子类型知识来注释抽象类,并通过@JsonTypeInfo 提供区分字段:

      https://stackoverflow.com/a/27183383/1563240

      【讨论】:

        【解决方案3】:

        您可以通过杰克逊映射来映射类。在类中添加类型属性。

        @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "type")
        @JsonSubTypes({@JsonSubTypes.Type(value = ContractUpdateMessage.class, name = "ContractUpdateMessage"),             
                       @JsonSubTypes.Type(value = SingleContractUpdateMessage.class, name = "SingleContractUpdateMessage")})
        public abstract class DlqMessage {
        

        【讨论】:

          猜你喜欢
          • 2016-10-23
          • 2020-03-07
          • 2021-08-04
          • 2018-11-16
          • 2013-05-21
          • 1970-01-01
          • 2018-10-29
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多