Flink CDC系列之:JSON 序列化器JsonRowDataSerializationSchemaUtils

这是一个 Flink 版本兼容性工具类,专门用于处理不同 Flink 版本之间 JSON 序列化器的构造函数差异。

类概述

java 复制代码
public class JsonRowDataSerializationSchemaUtils

核心问题:Flink 1.19 和 1.20 版本中 JsonRowDataSerializationSchema 和 RowDataToJsonConverters 的构造函数参数数量不同,导致版本兼容性问题。

核心方法详解

createSerializationSchema() - 创建 JSON 序列化器

java 复制代码
public static JsonRowDataSerializationSchema createSerializationSchema(
        RowType rowType,
        TimestampFormat timestampFormat,
        JsonFormatOptions.MapNullKeyMode mapNullKeyMode,
        String mapNullKeyLiteral,
        boolean encodeDecimalAsPlainNumber,
        boolean ignoreNullFields) {

参数说明:

  • rowType:行数据类型定义
  • timestampFormat:时间戳格式(SQL/ISO-8601)
  • mapNullKeyMode:Map中null键的处理模式
  • mapNullKeyLiteral:null键的替换文本
  • encodeDecimalAsPlainNumber:是否将Decimal编码为普通数字
  • ignoreNullFields:是否忽略null字段(Flink 1.20新增)

兼容性处理逻辑

java 复制代码
try {
    Class<?>[] fullParams = new Class[] {
        RowType.class,
        TimestampFormat.class,
        JsonFormatOptions.MapNullKeyMode.class,
        String.class,
        boolean.class,
        boolean.class  // Flink 1.20 新增参数
    };

    Object[] fullParamValues = new Object[] {
        rowType,
        timestampFormat,
        mapNullKeyMode,
        mapNullKeyLiteral,
        encodeDecimalAsPlainNumber,
        ignoreNullFields
    };

    // 从6个参数开始尝试,逐步减少到5个参数
    for (int i = fullParams.length; i >= 5; i--) {
        try {
            // 尝试获取指定参数数量的构造函数
            Constructor<?> constructor =
                    JsonRowDataSerializationSchema.class.getConstructor(
                            Arrays.copyOfRange(fullParams, 0, i));

            // 使用对应数量的参数创建实例
            return (JsonRowDataSerializationSchema)
                    constructor.newInstance(Arrays.copyOfRange(fullParamValues, 0, i));
        } catch (NoSuchMethodException ignored) {
            // 如果找不到构造函数,继续尝试更少的参数
        }
    }
} catch (Exception e) {
    throw new RuntimeException("版本兼容性错误", e);
}

工作流程:

  • 首先尝试 Flink 1.20 的 6 参数构造函数
  • 如果失败,尝试 Flink 1.19 的 5 参数构造函数
  • 根据实际存在的构造函数创建实例

createRowDataToJsonConverters() - 创建 JSON 转换器

java 复制代码
public static RowDataToJsonConverters createRowDataToJsonConverters(
        TimestampFormat timestampFormat,
        JsonFormatOptions.MapNullKeyMode mapNullKeyMode,
        String mapNullKeyLiteral,
        boolean ignoreNullFields) {

参数说明:

  • timestampFormat:时间戳格式
  • mapNullKeyMode:Map中null键的处理
  • mapNullKeyLiteral:null键替换文本
  • ignoreNullFields:是否忽略null字段(Flink 1.20新增)

兼容性处理逻辑

java 复制代码
Class<?>[] fullParams = new Class[] {
    TimestampFormat.class,
    JsonFormatOptions.MapNullKeyMode.class,
    String.class,
    boolean.class  // Flink 1.20 新增参数
};

// 从4个参数开始尝试,逐步减少到3个参数
for (int i = fullParams.length; i >= 3; i--) {
    try {
        Constructor<?> constructor =
                RowDataToJsonConverters.class.getConstructor(
                        Arrays.copyOfRange(fullParams, 0, i));
        return (RowDataToJsonConverters)
                constructor.newInstance(Arrays.copyOfRange(fullParamValues, 0, i));
    } catch (NoSuchMethodException ignored) {
    }
}

enableIgnoreNullFields() - 检查是否支持忽略null字段

java 复制代码
public static boolean enableIgnoreNullFields(ReadableConfig formatOptions) {
    try {
        // 通过反射检查 Flink 版本是否支持 ENCODE_IGNORE_NULL_FIELDS 配置
        Field field = JsonFormatOptions.class.getField("ENCODE_IGNORE_NULL_FIELDS");
        ConfigOption<Boolean> encodeOption = (ConfigOption<Boolean>) field.get(null);
        return formatOptions.get(encodeOption);
    } catch (NoSuchFieldException | IllegalAccessException e) {
        // 如果字段不存在,说明是 Flink 1.19,不支持此功能
        return false;
    }
}

设计模式:反射适配器模式

这个工具类使用了反射适配器模式来解决版本兼容性问题:

传统方式的问题

java 复制代码
// 传统方式:需要根据版本写条件判断
if (flinkVersion >= "1.20") {
    return new JsonRowDataSerializationSchema(..., ignoreNullFields);
} else {
    return new JsonRowDataSerializationSchema(...); // 5个参数
}
  • 反射适配器的优势
  • 无需硬编码版本检查
  • 运行时动态发现可用构造函数
  • 支持未来版本扩展

总结

这个 JsonRowDataSerializationSchemaUtils 工具类:

✅ 解决版本兼容性:处理 Flink 1.19 和 1.20 的构造函数差异

✅ 使用反射适配器:运行时动态发现可用构造函数

✅ 提供统一接口:对上层代码隐藏版本差异

✅ 良好的错误处理:清晰的错误信息和版本要求提示

✅ 未来可移除:设计为临时解决方案,便于后续清理

这是一个典型的版本兼容性适配器设计,在开源库升级过程中经常使用,确保代码在不同版本依赖下都能正常工作。

代码解释

java 复制代码
for (int i = fullParams.length; i >= 3; i--) {
    try {
        Constructor<?> constructor =
                RowDataToJsonConverters.class.getConstructor(
                        Arrays.copyOfRange(fullParams, 0, i));
        return (RowDataToJsonConverters)
                constructor.newInstance(Arrays.copyOfRange(fullParamValues, 0, i));
    } catch (NoSuchMethodException ignored) {
    }
}

这段代码是一个动态构造函数查找与实例化的逻辑,用于在运行时尝试使用不同数量的参数构造 RowDataToJsonConverters 类的对象。下面是对它的逐步解析:

java 复制代码
for (int i = fullParams.length; i >= 3; i--) {

从 fullParams 数组的长度开始倒序循环,直到 i 等于 3。

这意味着它首先尝试用最多的参数去匹配构造函数,然后逐渐减少参数数量,但至少保留前三个参数

尝试获取构造函数

java 复制代码
Constructor<?> constructor =
    RowDataToJsonConverters.class.getConstructor(
        Arrays.copyOfRange(fullParams, 0, i));

每次循环中,通过反射获取 RowDataToJsonConverters 类的一个构造函数。

Arrays.copyOfRange(fullParams, 0, i) 截取 fullParams 数组中索引 0 到 i-1 的元素作为构造函数的参数类型列表。

成功时立即返回实例

java 复制代码
return (RowDataToJsonConverters)
    constructor.newInstance(Arrays.copyOfRange(fullParamValues, 0, i));

一旦找到匹配的构造函数,就使用对应的参数值(同样截取自 fullParamValues)创建实例并返回。

由于是在 try 块内直接返回,所以只要找到合适的构造函数,就会跳出整个方法。

目的与应用场景

这种模式通常出现在库或框架的初始化过程中,目的是为了向后兼容------新版本的类可能新增了构造参数,而旧版本只有较少的参数。通过这种方式,可以同时支持新旧不同的调用方式。

例如,假设 RowDataToJsonConverters 有以下几个构造函数:

java 复制代码
RowDataToJsonConverters(String a, String b, String c)
RowDataToJsonConverters(String a, String b, String c, boolean d)

当传入的 fullParams 包含四个参数时,它会先尝试用全部四个参数去构建;若失败(比如在只定义了三个参数的旧版本中),则会回退到使用前三个参数去构建。

相关推荐
胖头鱼的鱼缸(尹海文)1 天前
胖头鱼的技术专栏-474 从能跑到能上线:PG SplitJSON 0.2.0 和 0.1.0 差在哪(20261008)
postgresql·json
?? Daisy1 天前
Claude Code 接入 GPT:settings.json 两种配置方法(2026-10)
gpt·json
小静AI工程实验室1 天前
Python 爬虫解析 JSON-LD:多块 script、@graph 与坏数据的 9 个边界
爬虫·python·json
goehou2 天前
LLM 结构化输出全解:从 Prompt 约束到 Schema 硬保证,三层实现怎么选
ai·llm·json·agent·教程·结构化输出
独立开发者阿乐2 天前
结构化数据 JSON-LD 实战:让 AI 读懂你的网页
人工智能·json·结构化数据·geo优化·json-ld·ai收录·faqpage
liangshanbo12154 天前
高级前端面试题:package.json 常见字段
前端·json
梦帮科技5 天前
【3.0修订版】创世与节点工程:genesis.json 与 node.toml 解剖
数据结构·算法·json·去中心化·区块链·智能合约·信任链
weixin_440730505 天前
内置函数、json文本、pickle二进制
开发语言·python·json
念越5 天前
接口自动化测试从入门到实战:接口用例设计、Requests、Pytest、YAML、JSON Schema 与 Allure 报告
python·测试工具·自动化·json·pytest
福兮说6 天前
前后端算的 MD5、SHA-256 对不上?编码、换行、BOM、HMAC、JSON 顺序,八个原因逐个实测
前端·javascript·node.js·json·哈希算法