升级springboot 4.0.1的坑——序列化
前文提到了升级springboot到 4.0.1遇到的编译和启动的问题,后续测试中还发现一个大坑——序列化问题。
springboot 4.0.1除了升级spring相关的版本依赖之外,还对其他基础依赖版本进行了大幅度的更新,其中就包含了jackson。
springboot 4.0.1直接将jackson依赖升级到了jackson3,由于jackson3的group id修改了,因此我们的业务代码在编译、应用启动的时候都没有问题, 但是涉及到web层对对象进行反序列化的时候,就遇到了问题。
背景
之前的代码中,一些业务pojo设计了一系列复杂的继承和组装关系,导致将json反序列化成Java对象的时候,需要依赖JSON中定义的类型字段。
因此针对Jackson,之前的代码有一整套基于自定义的type字段到实际反序列化Java类的定义,这些类定义会在Spring初始化的时候进行初始化,
并注册到Jackson的SimpleModule模块中,同时初始化一个自定义的ObjectMapper对象。也就是说,老的代码是通过扩展
com.fasterxml.jackson.databind.JsonDeserializer来实现的基于某个特定字段指定反序列化类,而不是通过jackson定义的
com.fasterxml.jackson.annotation.JsonTypeInfo注解来实现的。
因此,这部分实现逻辑,需要按照jackson3的API重新来进行装配。
改动
由于之前的实现已经保存了字段值和对应Java bean的映射关系,相关的类型很多,重新维护这个关系也很复杂,
因此需要将老版本的代码适配到新的tools.jackson.databind.ValueDeserializer接口的反序列化器,
同时,注册到spring web使用的jackson环境中。
首先实现这个代理:
public class Jackson3ValueDeserializeAdaptor<T extends Typed> extends ValueDeserializer<T> {
private final AbstractTypedJsonObjectDeserializer<T> delegate;
public Jackson3ValueDeserializeAdaptor(AbstractTypedJsonObjectDeserializer<T> delegate) {
this.delegate = delegate;
}
@Override
public T deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
JsonNode jsonNode = p.readValueAsTree();
JsonNode typeNode = jsonNode.get("type");
if (typeNode != null && typeNode.isValueNode()) {
String type = typeNode.asString();
Map<String, Class<? extends T>> typedMapper = delegate.getTypedMapper();
Class<? extends T> targetClass = typedMapper.get(type);
if (targetClass != null) {
return ctxt.readTreeAsValue(jsonNode, targetClass);
}
}
}
}
这里的AbstractTypedJsonObjectDeserializer是之前基于jackson2的反序列化实现,这里只是为了获取字段type的值和对应Pojo的映射关系。
然后在之前的管理器中,获取jackson2的反序列化实例,装配刚实现的适配器:
public Deserializers getJackson3Deserializers() {
return new Deserializers.Base() {
@Override
public boolean hasDeserializerFor(DeserializationConfig config, Class<?> valueType) {
return typedDeserializers.containsKey(valueType);
}
@Override
public ValueDeserializer<?> findBeanDeserializer(JavaType type, DeserializationConfig config,
Supplier beanDescRef) {
AbstractTypedJsonObjectDeserializer<?> deserializer =
typedDeserializers.get(type.getRawClass());
if (deserializer != null) {
return new Jackson3ValueDeserializeAdaptor<>(deserializer);
}
return null;
}
};
}
这里的typedDeserializers是之前初始化的时候,注册了所有已知类型反序列化器的Map,这里直接获取之后,包装成jackson3的
tools.jackson.databind.deser.Deserializers对象。
通过spring web使用的json转换器org.springframework.http.converter.json.JacksonJsonHttpMessageConverter,
的初始化代码可以发现,spring web初始化jackson3的JsonMapper的时候,是通过Java的SPI机制加载的模块(参考tools.jackson.databind.cfg.MapperBuilder#findModules(java.lang.ClassLoader方法的实现),
而不是通过springboot常用的自动装配来初始化的。
因此,最后还需要包装一个jackson3的SPI模块,用于JacksonJsonHttpMessageConverter的初始化:
public class HyperpaasJacksonModule extends JacksonModule {
@Override
public String getModuleName() {
return "HyperpaasJacksonModule";
}
@Override
public void setupModule(SetupContext context) {
// 从 JsonMapperManager 获取 Jackson 3 的 Deserializers
JsonMapperManager manager = JsonMapperManager.getInstance();
Deserializers deserializers =
manager.getJackson3Deserializers();
// 注册 Deserializers
context.addDeserializers(deserializers);
}
@Override
public Version version() {
return new Version(1, 0, 0, null,
"com.hyperpaas.platform", "commons-json-serializer");
}
}
除了代码之外,还需要创建SPI定义文件tools.jackson.databind.JacksonModule:
com.hyperpaas.platform.commons.json.serializer.HyperpaasJacksonModule
里面的内容就是模块类的全路径。
这样,jackson3就能够使用之前定义的映射关系来进行反序列化,同时,由于使用了SPI机制,不需要修改业务层的代码,就可以完成初始化。