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