背景
在实际项目开发中,随着业务复杂度的增加,前端请求体的结构往往会变得多样化。以文章阅读权限设置为例,阅读权限的判断条件可能涉及多种因素,例如用户等级、是否为付费用户、日期区间限制等。不同的判断条件在前端会通过不同的输入框或下拉框进行选择,因此传递到后端的字段也会有所不同。这种情况下,前端传递的请求体可能会变得非常复杂。例如:
{
"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;
}
虽然这种方法可行,但它存在一些明显的缺点:
- 违反开闭原则:每次新增一种权限类型时,都需要修改这个类,增加了代码的维护成本。
- 维护困难:在业务场景复杂的情况下,该类可能会包含大量字段,导致代码冗长且难以维护。
- 业务信息不单一:一个类中混杂了多种业务逻辑,降低了代码的可读性和可维护性。
- 大量
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:列出所有可能的子类及其对应的类型标识符
具体实现
- 定义业务枚举类
根据示例进行业务分析,阅读权限可能有如下的限制类型:用户等级(只有相应等级的用户才能阅读)、付费用户、日期区间(只有在该区间内的时间才能阅读)、积分数(用户需要有足够的积分才能阅读)等。这个枚举类定义了所有业务场景,当业务场景扩充时也需扩展此类。
@AllArgsConstructor
public enum ConditionType {
MEMBER_LEVEL("用户等级"),
PAID_MEMBER("付费用户"),
DATE_RANGE("日期区间"),
POINTS("积分数");
private String description;
}
- 定义父类
@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不同的值会反序列化为不同的子类。
- 定义子类
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种不同限制条件下的不同字段。
- 测试反序列化
@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 类中。这种方法不仅提高了代码的可维护性,还增强了系统的扩展性,是处理多样化请求体的理想选择。
在后续的博客中,我们会针对该场景继续讨论数据库的多态处理,以此完成多态场景下的全链路代码编写。
留言