返回

Spring Boot OpenAPI 多态:anyOf与discriminator详解

java

Spring Boot OpenAPI 多态定义:anyOf 与 discriminatorProperty

使用 Spring Boot 构建 RESTful API 时,经常会遇到处理多态数据的需求。 当你需要在 OpenAPI (Swagger) 文档中定义这种多态实体,特别是涉及到请求体 (RequestBody) 时,事情可能会变得有点棘手。 使用 anyOfdiscriminatorProperty 本应可以正确地进行类型推断,但有时会出现问题,无法实现预期效果。

问题分析:Jackson 反序列化难题

在上面给出的示例代码中,我们尝试使用 anyOf 定义 Parrot 接口可以由 PolarParrotNorwegianParrot 实现。discriminatorProperty 被设置为 parrotType,目的是帮助 Jackson 反序列化器根据请求体中的 parrotType 属性确定具体的实现类。

遇到的问题在于,Jackson 反序列化器尝试直接构造一个 Parrot 接口的实例,但由于接口本身不能直接实例化,这导致了 InvalidDefinitionExceptionanyOfdiscriminatorProperty 虽然定义了类型间的关系,却未能直接指导 Jackson 选择具体实现。

解决方案一:启用子类型注册

一种有效的解决办法是通过 ObjectMapper 的配置显式注册子类型。 Jackson 需要知道如何根据 discriminatorProperty 的值映射到具体的实现类。 可以在 Spring Boot 的配置类中完成这一操作,直接告诉 ObjectMapper 需要识别的 Parrot 的子类型:

操作步骤:

  1. 创建一个配置类,例如 JacksonConfig
  2. 在配置类中注入 ObjectMapper 并使用 registerSubtypes 方法注册子类型。
  3. @Bean 注解将 ObjectMapper 声明为一个可被 Spring 容器管理的 Bean,允许全局配置。

代码示例:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerSubtypes(new NamedType(PolarParrot.class, "polar"));
        mapper.registerSubtypes(new NamedType(NorwegianParrot.class, "norwegian"));
        return mapper;
    }
}

这段代码清晰地将字符串 "polar" 与 PolarParrot,字符串 "norwegian" 与 NorwegianParrot 关联,让 Jackson 在反序列化时能够正确选择。 这样,即使请求体只包含接口和 discriminatorProperty 的定义,Jackson 也知道如何正确反序列化。

解决方案二:添加 @JsonTypeInfo@JsonSubTypes 注解

另一种解决方案是直接在 Parrot 接口上使用 Jackson 提供的 @JsonTypeInfo@JsonSubTypes 注解,从而不需要手动配置 ObjectMapper 。这两个注解能够更加清晰和直观的表达出类的多态结构:

操作步骤:

  1. Parrot 接口上添加 @JsonTypeInfo@JsonSubTypes 注解。
  2. @JsonTypeInfo 定义类型信息包含在 parrotType 属性中。
  3. @JsonSubTypes 列举 Parrot 的具体子类,并通过 name 属性指定对应的字符串值。

代码示例:

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXISTING_PROPERTY,
        property = "parrotType",
        visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = PolarParrot.class, name = "polar"),
        @JsonSubTypes.Type(value = NorwegianParrot.class, name = "norwegian")
})
interface Parrot {
    String getParrotType();
    String getPlumage();
}

@JsonTypeInfo 配合 @JsonSubTypes 提供了一种在模型层面多态性的方法,代码更加简洁易读。 Jackson 能够利用这些注解直接将 parrotType 映射到对应的 Parrot 实现类。

安全提示

当处理来自外部的输入数据,包括用于多态解析的属性时,应该特别注意安全性问题:

  1. 输入校验: 务必校验输入中的 parrotType 是否合法。只接受预定义的字符串值,避免使用不受信任的值。 可以使用枚举代替字符串来进一步增强校验力度。
  2. 限制类型选择: 使用明确的枚举或者类型列表定义允许的子类型,避免使用通配符或者用户可控的值作为 discriminatorProperty 的值。这能够阻止用户尝试反序列化恶意的类,造成安全隐患。

以上方案和建议可以有效地帮助你解决 Spring Boot OpenAPI 多态定义中 anyOfdiscriminatorProperty 的使用问题。选择哪种方法取决于具体情况和团队的偏好。 请记住,清晰的定义和严格的校验对于构建健壮的 API 至关重要。