替换seata BusinessActionContext序列化方式,从根源上解决反序列化类型不一致问题

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 替换seata BusinessActionContext序列化方式,从根源上解决反序列化类型不一致问题

前言

在前面的文章发现Seata TCC模式的一个BUG,顺手给社区提了一个issue中,我在实际集成TCC的时候发现了BusinessActionContext在反序列化的时候,对于一些数据类型会产生序列化前后不一致的情况,另外在另一片文章给Seata TCC模式提了一个Issue,顺便说说我的解决思路中给出了我的另一个解决方案,但是这个解决方案有一点小小的瑕疵,它只能针对一阶段方法上面的参数类型进行正确的反序列化,无法针对开发人员在处理过程中添加进来的value类型进行处理,因为java的泛型在编译期结束后会被抹除,导致运行时是无法完全拿到开发人员put进来的数据类型的。

比如这种情况下:

List<Long> list = new ArrayList<>();
    list.add(10L);
    BusinessActionContextUtil.addContext("list",list);
复制代码

在运行时通过反射解析拿到的就是ArrayList.class,完全拿不到里面的子类型Long.class。所以说上述解决方案只能解决一部分问题,无法从根源上解决问题。

扩展序列化方式

为了从根本上解决上述问题,查了各种资料,同时咨询了seata社区的各位大佬,最终决定使用jackson序列化方式取代fastjson。之所以这么做,是因为jackson可以在序列化的时候解析出数据对应的类型,并正确地反序列化出来,这样就完美地解决了问题。虽说fastjson也可以通过autoType解析出数据类型,但是查询资料发现fastjson autoType的黑暗历史,实在是有点心慌慌。

另外,为了在改造后还能够继续兼容fastjson序列化的字符串,我们还不能直接替换掉fastjson,所以最终选择了扩展的方式来逐步完善。

jackson实现方式

为了能够和fastjson兼容,我们需要创建一个接口来抽象不同序列化方式的共同行为:

public interface ContextSerializer {
    // 序列化方式名称
    String getName();
    // 序列化成字节数组
    byte[] encode(Object value);
    // 序列化成string
    String encodeToString(Object value);
    // 反序列化
    <T> T decode(byte[] bytes, Class<T> clazz);
    // 反序列化
    <T> T decodeString(String string, Class<T> clazz);
}
复制代码

再来一个抽象类把一些重复代码给处理掉:

import java.nio.charset.StandardCharsets;
import java.util.Objects;
import io.seata.common.util.StringUtils;
/**
 * abstract context serializer
 *
 * @author zouwei
 */
public abstract class AbstractContextSerializer implements ContextSerializer {
    /**
     * encode object to bytes
     * 
     * @param value
     * @return
     */
    @Override
    public byte[] encode(Object value) {
        if (Objects.isNull(value)) {
            return null;
        }
        return doEncode(value);
    }
    /**
     * encode object to string
     * 
     * @param value
     * @return
     */
    @Override
    public String encodeToString(Object value) {
        byte[] bytes = encode(value);
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        return new String(encode(value), StandardCharsets.UTF_8);
    }
    /**
     * custom encode
     *
     * @param value
     * @return
     */
    protected abstract byte[] doEncode(Object value);
    /**
     * decode bytes to target class
     *
     * @param bytes
     * @param clazz
     * @param <T>
     * @return
     */
    @Override
    public <T> T decode(byte[] bytes, Class<T> clazz) {
        if (bytes == null || bytes.length <= 0 || Objects.isNull(clazz)) {
            return null;
        }
        return doDecode(bytes, clazz);
    }
    /**
     * decode string to target class
     * 
     * @param string
     * @param clazz
     * @param <T>
     * @return
     */
    @Override
    public <T> T decodeString(String string, Class<T> clazz) {
        if (StringUtils.isBlank(string) || Objects.isNull(clazz)) {
            return null;
        }
        return decode(string.getBytes(StandardCharsets.UTF_8), clazz);
    }
    /**
     * custom decode
     * 
     * @param bytes
     * @param clazz
     * @param <T>
     * @return
     */
    protected abstract <T> T doDecode(byte[] bytes, Class<T> clazz);
}
复制代码

最后就是jackson的具体实现了:

import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.seata.common.executor.Initialize;
import io.seata.common.loader.LoadLevel;
import io.seata.rm.tcc.serializer.AbstractContextSerializer;
/**
 * BusinessActionContext serialize by jackson serializer
 *
 * @author zouwei
 */
@LoadLevel(name = JacksonContextSerializer.NAME)
public class JacksonContextSerializer extends AbstractContextSerializer implements Initialize {
    public static final String NAME = "jackson";
    private static final Logger LOGGER = LoggerFactory.getLogger(JacksonContextSerializer.class);
    private final ObjectMapper mapper = new ObjectMapper();
    @Override
    public void init() {
        this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 这个是关键代码,通过这个配置,可以让jackson把数据类型也解析好并序列化
        this.mapper.activateDefaultTyping(this.mapper.getPolymorphicTypeValidator(),
            ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        this.mapper.enable(MapperFeature.PROPAGATE_TRANSIENT_MARKER);
    }
    @Override
    public String getName() {
        return NAME;
    }
    /**
     * serialize by jackson
     *
     * @param value
     * @return
     */
    @Override
    protected byte[] doEncode(Object value) {
        try {
            return this.mapper.writeValueAsBytes(value);
        } catch (JsonProcessingException e) {
            LOGGER.error("json toJsonString exception, {}", e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }
    /**
     * deserialize by jackson
     *
     * @param bytes
     * @param clazz
     * @param <T>
     * @return
     */
    @Override
    protected <T> T doDecode(byte[] bytes, Class<T> clazz) {
        try {
            return this.mapper.readValue(bytes, clazz);
        } catch (IOException e) {
            LOGGER.error("json parseObject exception, {}", e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }
}
复制代码

同样的,fastjson处理方式全部需要改造成实现ContextSerializer或者继承AbstractContextSerializer,这样才能通过接口的方式提供统一的序列化功能。

import java.nio.charset.StandardCharsets;
import java.util.Objects;
import com.alibaba.fastjson.JSON;
import io.seata.common.loader.LoadLevel;
import io.seata.rm.tcc.serializer.AbstractContextSerializer;
/**
 * BusinessActionContext serialize by fastjson serializer
 *
 * @author zouwei
 */
@LoadLevel(name = FastJsonContextSerializer.NAME)
public class FastJsonContextSerializer extends AbstractContextSerializer {
    public static final String NAME = "fastjson";
    @Override
    public String getName() {
        return NAME;
    }
    /**
     * serialize by fastjson
     *
     * @param value
     * @return
     */
    @Override
    protected byte[] doEncode(Object value) {
        if (value instanceof String) {
            return ((String)value).getBytes(StandardCharsets.UTF_8);
        }
        return JSON.toJSONBytes(value);
    }
    /**
     * deserialize by fastjson
     *
     * @param bytes
     * @param clazz
     * @param <T>
     * @return
     */
    @Override
    protected <T> T doDecode(byte[] bytes, Class<T> clazz) {
        if (Objects.equals(clazz, byte[].class)) {
            return (T)bytes;
        }
        return Objects.equals(clazz, String.class) ? (T)new String(bytes, StandardCharsets.UTF_8)
            : JSON.parseObject(bytes, clazz);
    }
}
复制代码

最后,为了能够通过配置的方式来选择统一的序列化方式,我们另外提供一个工厂类来创建序列化工具,里面涉及到了工厂设计模式和单例设计模式:

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import io.seata.common.loader.EnhancedServiceLoader;
import io.seata.common.util.CollectionUtils;
/**
 * tcc BusinessActionContext serializer factory
 *
 * @author zouwei
 */
public class ContextSerializerFactory {
    private ContextSerializerFactory() {}
    // ContextSerializer cache
    private static final ConcurrentMap<String, ContextSerializer> INSTANCES = new ConcurrentHashMap<>();
    /**
     * Singleton Holder
     */
    enum SingletonHolder {
        INSTANCE {
            @Override
            ContextSerializer getInstance() {
                return ContextSerializerFactory.getInstance(ContextConstants.DEFAULT_SERIALIZER);
            }
        };
        abstract ContextSerializer getInstance();
    }
    /**
     * get ContextSerializer
     * 
     * @return
     */
    public static ContextSerializer getInstance() {
        return SingletonHolder.INSTANCE.getInstance();
    }
    /**
     * get ContextSerializer by name
     * 
     * @param name
     * @return
     */
    public static ContextSerializer getInstance(String name) {
        return CollectionUtils.computeIfAbsent(INSTANCES, name,
            key -> EnhancedServiceLoader.load(ContextSerializer.class, name));
    }
}
复制代码

上述工厂类中,通过枚举的方式实现了懒加载的单例模式。

为了能够方便地替换掉代码中的fastjson序列化方式,我们需要创建一个统一的序列化类作为入口来管理所有的序列化方式:

import io.seata.rm.tcc.serializer.spi.FastJsonContextSerializer;
/**
 * Used to serialize and deserialize BusinessActionContext
 *
 * @author zouwei
 */
public class BusinessActionContextSerializer {
    /**
     * serialize Object value to json string by jackson
     * 
     * @param value
     * @return
     */
    public static String toJsonString(Object value) {
        ContextSerializer contextSerializer = ContextSerializerFactory.getInstance();
        return contextSerializer.encodeToString(value);
    }
    /**
     * deserialize json string by fastjson or jackson
     * 
     * @param json json string
     * @param clazz target class
     * @param <T>
     * @return
     */
    public static <T> T parseObject(String json, Class<T> clazz) {
        T result;
        // json string start with "{"@class":", it will deserialize by jackson
        if (json.startsWith("{"@class":")) {
            ContextSerializer defaultContextSerializer = ContextSerializerFactory.getInstance();
            result = defaultContextSerializer.decodeString(json, clazz);
        } else {
            ContextSerializer fastjsonContextSerializer =
                ContextSerializerFactory.getInstance(FastJsonContextSerializer.NAME);
            result = fastjsonContextSerializer.decodeString(json, clazz);
        }
        return result;
    }
}
复制代码

我们对外目前就提供序列化和反序列化两个方法,序列化方式默认jackson方式,反序列化的时候,会根据对应的json字符串中是否以{"@class":开头,如果以{"@class":开头的话,那么就使用jackson方式反序列化,否则就用fastjson方式反序列化。

小结

整篇文章最关键的点在于如何处理开发人员自己putBusinessActionContext中的数据类型。通过了解到jackson能够把数据类型也序列化到json字符串中,并且能够正确地反序列化回来,并保持数据类型前后一致,我们为了在解决这个问题的同时保持对fastjson的兼容,所以做出了扩展BusinessActionContext序列化方式的决定。预计将来在seata1.6以后的版本中,将会解决BusinessActionContext序列化导致数据类型不一致的问题。


相关文章
|
18天前
|
JSON 数据格式 索引
Python中序列化/反序列化JSON格式的数据
【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。
|
28天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
1月前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
|
1月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第3天】在Java编程的世界里,对象序列化与反序列化是实现数据持久化和网络传输的关键技术。本文将深入探讨Java序列化的原理、应用场景以及如何通过代码示例实现对象的序列化与反序列化过程。从基础概念到实践操作,我们将一步步揭示这一技术的魅力所在。
|
28天前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
27 0
|
2月前
|
JSON fastjson Java
niubility!即使JavaBean没有默认无参构造器,fastjson也可以反序列化。- - - - 阿里Fastjson反序列化源码分析
本文详细分析了 Fastjson 反序列化对象的源码(版本 fastjson-1.2.60),揭示了即使 JavaBean 沲有默认无参构造器,Fastjson 仍能正常反序列化的技术内幕。文章通过案例展示了 Fastjson 在不同构造器情况下的行为,并深入探讨了 `ParserConfig#getDeserializer` 方法的核心逻辑。此外,还介绍了 ASM 字节码技术的应用及其在反序列化过程中的角色。
79 10
|
2月前
|
存储 XML JSON
用示例说明序列化和反序列化
用示例说明序列化和反序列化
|
2月前
|
JSON 缓存 NoSQL
redis序列化数据时,如何包含clsss类型信息?
通过配置 `com.fasterxml.jackson.databind.ObjectMapper` 的 `enableDefaultTyping` 方法,可以使序列化后的 JSON 包含类信息。
52 2
|
2月前
|
存储 Java 开发者
Java编程中的对象序列化与反序列化
【9月更文挑战第20天】在本文中,我们将探索Java编程中的一个核心概念——对象序列化与反序列化。通过简单易懂的语言和直观的代码示例,你将学会如何将对象状态保存为字节流,以及如何从字节流恢复对象状态。这不仅有助于理解Java中的I/O机制,还能提升你的数据持久化能力。准备好让你的Java技能更上一层楼了吗?让我们开始吧!
|
2月前
|
JSON 安全 编译器
扩展类实例的序列化和反序列化
扩展类实例的序列化和反序列化
35 0

热门文章

最新文章

下一篇
无影云桌面