内容纲要

背景

在实际项目开发中,随着业务复杂度的增加,前端请求体的结构往往会变得多样化。以文章阅读权限设置为例,阅读权限的判断条件可能涉及多种因素,例如用户等级、是否为付费用户、日期区间限制等。不同的判断条件在前端会通过不同的输入框或下拉框进行选择,因此传递到后端的字段也会有所不同。这种情况下,前端传递的请求体可能会变得非常复杂。例如:

{
  "articleId": 1,
  "type": "MEMBER_LEVEL",  //阅读权限的设置为用户等级,只有对应等级的用户才能阅读该文章
  "minLevel": 3           //最低需要3级用户才能阅读
}
{
  "articleId": 1,
  "type": "PAID_MEMBER",
  "isPaidMember": true        //付费会员才有阅读权限
}

面对这种多样化的请求体结构,后端如何高效地反序列化并处理这些数据成为了一个关键问题。最直接的做法是为每一种可能的字段组合定义一个类,例如:

public class ConditionRequest {
  private int articleId;
  private String type;
  private int minLevel;
  private boolean isPaidMember;
}

虽然这种方法可行,但它存在一些明显的缺点:

  1. 违反开闭原则:每次新增一种权限类型时,都需要修改这个类,增加了代码的维护成本。
  2. 维护困难:在业务场景复杂的情况下,该类可能会包含大量字段,导致代码冗长且难以维护。
  3. 业务信息不单一:一个类中混杂了多种业务逻辑,降低了代码的可读性和可维护性。
  4. 大量 if-else 代码块:针对不同的 type 值,可能需要编写大量的条件判断逻辑,进一步增加代码的复杂度。

为了避免硬编码带来的这些问题,Jackson 的多态反序列化功能提供了一种优雅的解决方案。通过多态反序列化,我们可以根据请求体中的 type 字段动态地将数据映射到不同的子类中,从而避免字段冗余和硬编码问题。接下来,我们将通过具体的示例,展示如何在业务场景中使用 Jackson 的多态反序列化功能来处理复杂的前端请求体。

技术实现

简介

Jackson 是一个广泛使用的 Java 库,用于处理 JSON 数据。它提供了强大的功能,包括 JSON 的序列化(将 Java 对象转换为 JSON 字符串)和反序列化(将 JSON 字符串转换为 Java 对象)。

在Jackson的注解中有两个核心注解可以用来实现多态反序列化:@JsonTypeInfo@JsonSubTypes。通过这两个注解,我们可以根据 JSON 中的某个字段(如 type)动态地选择反序列化的目标类。

  • @JsonTypeInfo:指定如何序列化或反序列化
    • use:指定类型信息的标识方式。常用的值是 JsonTypeInfo.Id.NAME,表示名称作为类型标识符。
    • include:指定类型信息在 JSON 中的包含方式。常用的值是 JsonTypeInfo.As.PROPERTY,表示类型信息作为 JSON 的一个属性。
    • property:指定 JSON 中用于标识类型的字段名(如 type)。
    • visible:指定类型信息是否在反序列化后仍然可见。默认为 false,通常设置为 true 以便在反序列化后访问类型信息。
  • @JsonSubTypes:列出所有可能的子类及其对应的类型标识符

具体实现

  1. 定义业务枚举类

根据示例进行业务分析,阅读权限可能有如下的限制类型:用户等级(只有相应等级的用户才能阅读)、付费用户、日期区间(只有在该区间内的时间才能阅读)、积分数(用户需要有足够的积分才能阅读)等。这个枚举类定义了所有业务场景,当业务场景扩充时也需扩展此类。

@AllArgsConstructor
public enum ConditionType {
    MEMBER_LEVEL("用户等级"),
    PAID_MEMBER("付费用户"),
    DATE_RANGE("日期区间"),
    POINTS("积分数");

    private String description;
}
  1. 定义父类
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "type",
        visible = true)
@JsonSubTypes(value = {
        @JsonSubTypes.Type(value = MemberLevelConditionRequestRequest.class, name = "MEMBER_LEVEL"),
        @JsonSubTypes.Type(value = PaidMemberConditionRequestRequest.class, name = "PAID_MEMBER"),
        @JsonSubTypes.Type(value = DateRangeConditionRequestRequest.class, name = "DATE_RANGE"),
        @JsonSubTypes.Type(value = PointsConditionRequestRequest.class, name = "POINTS")
})
public abstract class BaseConditionRequest {
    private Integer id;
    private Integer articleId;
    private ConditionType type;
    private LocalDateTime createTime;
}

在该示例中,定义type字段作为反序列化的依据。根据type不同的值会反序列化为不同的子类。

  1. 定义子类
public class MemberLevelConditionRequestRequest extends BaseConditionRequest {
    private Integer minLevel;
    private Integer maxLevel;
}
public class PaidMemberConditionRequestRequest extends BaseConditionRequest {
    private boolean isPaidMember;
}
public class DateRangeConditionRequestRequest extends BaseConditionRequest {
    private LocalDate startDate;
    private LocalDate endDate;
}
public class PointsConditionRequestRequest extends BaseConditionRequest {
    private int points;
}

这4个子类分别对应4种不同限制条件下的不同字段。

  1. 测试反序列化
@Test
void should_deserialize_request_to_sub_class() throws JsonProcessingException {
    String json1 = "{\n" +
            "    \"type\": \"MEMBER_LEVEL\",\n" +
            "    \"minLevel\": 1,\n" +
            "    \"maxLevel\": 10\n" +
            "}";
    ObjectMapper objectMapper = new ObjectMapper();

    MemberLevelConditionRequestRequest memberLevelConditionRequest = (MemberLevelConditionRequestRequest) objectMapper.readValue(json1, BaseConditionRequest.class);
    assertEquals(1, memberLevelConditionRequest.getMinLevel());
    assertEquals(10, memberLevelConditionRequest.getMaxLevel());

    String json2 = "{\n" +
            "    \"type\": \"POINTS\",\n" +
            "    \"points\": 10\n" +
            "}";
    PointsConditionRequestRequest pointsConditionRequest = (PointsConditionRequestRequest) objectMapper.readValue(json2, BaseConditionRequest.class);
    assertEquals(10, pointsConditionRequest.getPoints());
}

可以看到在实现了多态反序列化后,对于不同业务场景的json字符串可以转化成对应的子类,在后续的业务操作过程中就可以避免硬编码带来的缺点了。

总结

通过 Jackson 的 @JsonTypeInfo@JsonSubTypes 注解,我们可以轻松实现多态反序列化,将复杂的前端请求体映射到不同的 Java 类中。这种方法不仅提高了代码的可维护性,还增强了系统的扩展性,是处理多样化请求体的理想选择。

在后续的博客中,我们会针对该场景继续讨论数据库的多态处理,以此完成多态场景下的全链路代码编写。

最后修改日期: 2025年2月15日

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。