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

简介: 替换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序列化导致数据类型不一致的问题。


相关文章
|
4天前
|
存储 XML JSON
数据传输的艺术:深入探讨序列化与反序列化
数据传输的艺术:深入探讨序列化与反序列化
74 0
|
4天前
|
存储 C#
C#中的序列化和反序列化
C#中的序列化和反序列化
23 0
|
4天前
|
存储 Java 数据库
|
4天前
|
存储 安全 Java
Java一分钟之-Java序列化与反序列化
【5月更文挑战第14天】Java序列化用于将对象转换为字节流,便于存储和网络传输。实现`Serializable`接口使类可被序列化,但可能引发隐私泄露、版本兼容性和性能问题。要避免这些问题,可使用`transient`关键字、控制`serialVersionUID`及考虑使用安全的序列化库。示例代码展示了如何序列化和反序列化对象,强调了循环引用和未实现`Serializable`的错误。理解并妥善处理这些要点对优化代码至关重要。
14 1
|
4天前
|
JSON 安全 Java
Spring Boot 序列化、反序列化
本文介绍了Spring Boot中的序列化和反序列化。Java提供默认序列化机制,通过实现Serializable接口实现对象到字节流的转换。Spring Boot默认使用Jackson处理JSON,可通过注解和配置自定义规则。然而,序列化可能引发安全问题,建议使用白名单、数据校验和安全库。最佳实践包括使用标准机制、自定义规则及注意版本控制。文章还提醒关注性能并提供了相关参考资料。
67 2
|
4天前
|
XML 存储 JSON
c#XML、JSON的序列化和反序列化,看完你就懂了
c#XML、JSON的序列化和反序列化,看完你就懂了
28 0
|
4天前
|
JSON Java Linux
【探索Linux】P.30(序列化和反序列化 | JSON序列化库 [ C++ ] )
【探索Linux】P.30(序列化和反序列化 | JSON序列化库 [ C++ ] )
23 2
|
4天前
|
XML 存储 JSON
[计算机网络]---序列化和反序列化
[计算机网络]---序列化和反序列化
|
4天前
|
存储 JSON PHP
python序列化与反序列化
python序列化与反序列化
|
4天前
|
存储 Java 测试技术
滚雪球学Java(22):序列化和反序列化
【4月更文挑战第11天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
35 1
滚雪球学Java(22):序列化和反序列化

热门文章

最新文章