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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 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序列化导致数据类型不一致的问题。


相关文章
|
2月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
6天前
|
存储 XML JSON
用示例说明序列化和反序列化
用示例说明序列化和反序列化
|
14天前
|
JSON fastjson Java
niubility!即使JavaBean没有默认无参构造器,fastjson也可以反序列化。- - - - 阿里Fastjson反序列化源码分析
本文详细分析了 Fastjson 反序列化对象的源码(版本 fastjson-1.2.60),揭示了即使 JavaBean 沲有默认无参构造器,Fastjson 仍能正常反序列化的技术内幕。文章通过案例展示了 Fastjson 在不同构造器情况下的行为,并深入探讨了 `ParserConfig#getDeserializer` 方法的核心逻辑。此外,还介绍了 ASM 字节码技术的应用及其在反序列化过程中的角色。
42 10
|
11天前
|
JSON 缓存 NoSQL
redis序列化数据时,如何包含clsss类型信息?
通过配置 `com.fasterxml.jackson.databind.ObjectMapper` 的 `enableDefaultTyping` 方法,可以使序列化后的 JSON 包含类信息。
33 2
|
14天前
|
存储 Java 开发者
Java编程中的对象序列化与反序列化
【9月更文挑战第20天】在本文中,我们将探索Java编程中的一个核心概念——对象序列化与反序列化。通过简单易懂的语言和直观的代码示例,你将学会如何将对象状态保存为字节流,以及如何从字节流恢复对象状态。这不仅有助于理解Java中的I/O机制,还能提升你的数据持久化能力。准备好让你的Java技能更上一层楼了吗?让我们开始吧!
|
22天前
|
存储 Java
Java编程中的对象序列化与反序列化
【9月更文挑战第12天】在Java的世界里,对象序列化与反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何通过实现Serializable接口来标记一个类的对象可以被序列化,并探索ObjectOutputStream和ObjectInputStream类的使用,以实现对象的写入和读取。我们还将讨论序列化过程中可能遇到的问题及其解决方案,确保你能够高效、安全地处理对象序列化。
|
5天前
|
JSON 安全 编译器
扩展类实例的序列化和反序列化
扩展类实例的序列化和反序列化
12 0
|
11天前
|
XML Dubbo Java
分布式-序列化,反序列化
分布式-序列化,反序列化
|
2月前
|
存储 Java
Java编程中的对象序列化与反序列化
【8月更文挑战第28天】在Java世界中,对象序列化与反序列化是数据持久化和网络传输的关键技术。本文将深入浅出地探讨这一过程,带你领略其背后的原理及应用,让你的程序在数据的海洋中自由航行。
|
1月前
|
存储 Java
Java编程中的对象序列化与反序列化
【9月更文挑战第2天】在Java的世界里,对象序列化和反序列化就像是给数据穿上了一件隐形的斗篷。它们让数据能够轻松地穿梭于不同的系统之间,无论是跨越网络还是存储在磁盘上。本文将揭开这层神秘的面纱,带你领略序列化和反序列化的魔法,并展示如何通过代码示例来施展这一魔法。
18 0

热门文章

最新文章

下一篇
无影云桌面