暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

【SpringMVC】从Fastjson迁移到Jackson,以及对技术选型的反思

原创 zhangyfr 2022-12-26
1226

为什么要换掉fastjson

直接原因是fastjson无法支持注解形式的自定义序列化和反序列化,虽然其Github上的Wiki上说明是支持的。但是实测结果表明:Test类的序列化被fastjson的ASMFactory生成字节码形式的序列化类代理,序列化的逻辑依然为原生而不是自定义的XXX.class

class Test{
    @JSONField(usingSerializer=XXX.class,usingDeserializer=YYY.class)
    private List<A> as;
    //省略
}

在官方Wiki上没有找到关闭ASM特性的方法,自己尝试在构造FastJsonHttpMessageConverter时用Feature.DisableASM也没有效果。

<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4" id="fastJsonHttpMessageConverter">
      <property name="fastJsonConfig">
          <bean class="com.alibaba.fastjson.support.config.FastJsonConfig">
              <property name="features">
                  <list>
                      <value>DisableASM</value>
                  </list>
              </property>
      </property>
  </bean>

总的来说,当使用JSON类库一些高级特性的时候,fastjson的表现没有jackson稳定,文档支持较之也不完善。如果项目不在意Json序列化/反序列化这块的性能,换用jackson是一种合理的选择。

关于技术选型的一点反思

根据网络上公开的资料比较得到下表。看起来不如不使用需要额外配置的高级特性,fastjson有官方的中文支持,学习难度也很低。


fastjson
jackson
上手容易度容易中等
高级特性支持中等丰富
官方文档、Example支持中文、少、无体系英文、较多
非官方资料高级特性用例少、stackoverflow上问题少中文资料较多

但是实际使用下来,fastjson的高级特性支持不完善、存在bug、得不到英文社区的支持,导致自己在业务实际应用时反而需要较多的时间去调试定位类库本身的问题。因此技术选型的时候要注意不能贪图容易上手,最好考虑其社区活跃度和网络上各种渠道的资料丰富程度,因为这在一定程度上展示了类库的健壮性。

如何兼容fastjson的一些特性

方便的序列化特性

fastjson提供了一些方便的序列化特性,下面设置的serializerFeatures特性主要针是对null的默认值处理。

<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4" id="fastJsonHttpMessageConverter">
<property name="fastJsonConfig">
<bean class="com.alibaba.fastjson.support.config.FastJsonConfig">
<property name="serializerFeatures">
<list>
<value>WriteMapNullValue</value> <!--输出Map中的null值 -->
<value>WriteNullListAsEmpty</value> <!-- 引用为null的列表/数组/集合输出为[] -->
<value>WriteNullStringAsEmpty</value> <!-- 引用为null的String输出为“” -->
<value>WriteNullNumberAsZero</value> <!-- 引用为null的数字类型输出为0 -->
<value>WriteNullBooleanAsFalse</value> <!-- 引用为null的Boolean输出为false -->
</list>
</property>
</bean>
</property>
</bean>


遗憾的是,jackson并没有提供如此方便的设置,jackson的核心类ObjectMapper暴露的NullSerializer无法针对不同类型设定不同的默认值,所幸jackson有完善的扩展机制。BeanSerializerModifierchangeProperties()方法提供了细粒度控制每个Field的序列化行为的可能,代码如下。


public enum SerializerFeature {
WriteNullListAsEmpty,
WriteNullStringAsEmpty,
WriteNullNumberAsZero,
WriteNullBooleanAsFalse;

public final int mask;

SerializerFeature() {
mask = (1 << ordinal());
}
}

final public static class FastJsonSerializerFeatureCompatibleForJackson extends BeanSerializerModifier {
final private JsonSerializer<Object> nullBooleanJsonSerializer;
final private JsonSerializer<Object> nullNumberJsonSerializer;
final private JsonSerializer<Object> nullListJsonSerializer;
final private JsonSerializer<Object> nullStringJsonSerializer;

FastJsonSerializerFeatureCompatibleForJackson(SerializerFeature... features) {
int config = 0;
for (SerializerFeature feature : features) {
config |= feature.mask;
}
nullBooleanJsonSerializer = (config & WriteNullBooleanAsFalse.mask) != 0 ? new NullBooleanSerializer() : null;
nullNumberJsonSerializer = (config & WriteNullNumberAsZero.mask) != 0 ? new NullNumberSerializer() : null;
nullListJsonSerializer = (config & WriteNullListAsEmpty.mask) != 0 ? new NullListJsonSerializer() : null;
nullStringJsonSerializer = (config & WriteNullStringAsEmpty.mask) != 0 ? new NullStringSerializer() : null;
}

@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
for (BeanPropertyWriter writer : beanProperties) {
final JavaType javaType = writer.getType();
final Class<?> rawClass = javaType.getRawClass();
if (javaType.isArrayType() || javaType.isCollectionLikeType()) {
writer.assignNullSerializer(nullListJsonSerializer);
} else if (Number.class.isAssignableFrom(rawClass) && rawClass.getName().startsWith("java.lang")) {
writer.assignNullSerializer(nullNumberJsonSerializer);
} else if (Boolean.class.equals(rawClass)) {
writer.assignNullSerializer(nullBooleanJsonSerializer);
} else if (String.class.equals(rawClass)) {
writer.assignNullSerializer(nullStringJsonSerializer);
}
}
return beanProperties;
}

private static class NullListJsonSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeStartArray();
jgen.writeEndArray();
}
}

private static class NullNumberSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeNumber(0);
}
}

private static class NullBooleanSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeBoolean(false);
}
}

private static class NullStringSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString("");
}
}
}

最后把该Modifier注入到ObjectMapper中。

objectMapper.setSerializerFactory(objectMapper.getSerializerFactory().withSerializerModifier(new FastJsonSerializerFeatureCompatibleForJackson(features)));

反序列化时忽略不存在的字段

在标准Json规范中,如果尝试反序列化一个Json字符串为指定的POJO,而且字符串中有一个Field在POJO中不存在,应该抛出错误。对于这种情况,fastjson默认是跳过,jackson默认是中止。

objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

重新设置SpringMVC的HttpMessageConvertor

<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
      <property name="objectMapper">
          <bean class="com.dianping.orderdish.framework.util.CustomJacksonGenerator.CustomObjectMapperFactoryBean">
              <constructor-arg>
                  <util:list>
                      <value>WriteNullListAsEmpty</value>
                      <value>WriteNullStringAsEmpty</value>
                      <value>WriteNullNumberAsZero</value>
                      <value>WriteNullBooleanAsFalse</value>
                  </util:list>
              </constructor-arg>
          </bean>
      </property>
  </bean>

由于需要编程式的设置全局配置,扩展FactoryBeanCustomObjectMapperFactoryBean)生成自定义的ObjectMapper来替代MappingJackson2HttpMessageConverter中默认ObjectMapper

final public static class CustomObjectMapperFactoryBean implements FactoryBean<ObjectMapper> {
 SerializerFeature[] features;
 public CustomObjectMapperFactoryBean(SerializerFeature[] features) {
     this.features = features;
 }

 @Override
 public ObjectMapper getObject() throws Exception {
     ObjectMapper objectMapper = new ObjectMapper();
     objectMapper.setSerializerFactory(objectMapper.getSerializerFactory().withSerializerModifier(new FastJsonSerializerFeatureCompatibleForJackson(features))).configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
     return objectMapper;
 }

 @Override
 public Class<?> getObjectType() {
     return ObjectMapper.class;
 }

 @Override
 public boolean isSingleton() {
     return false;
 }
}











「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论