为什么要换掉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(newFastJsonSerializerFeatureCompatibleForJackson(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;
}
}

Reference

  1. 【私人定制jackson】定制jackson的自定义序列化(null值的处理)
  2. Jackson Documentation

【SpringMVC】从Fastjson迁移到Jackson,以及对技术选型的反思的更多相关文章

  1. springmvc整合fastjson

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  2. 【SSM之旅】Spring+SpringMVC+MyBatis+Bootstrap整合基础篇(一)项目简介及技术选型相关介绍

    试水 一直想去搭建个自己的个人博客,苦于自己的技术有限,然后也个人也比较懒散.想动而不能动,想动而懒得动,就这么一直拖到了现在.总觉得应该把这几年来的所学总结一番,这样才能有所成长. 不知在何时,那就 ...

  3. Spring+SpringMVC+MyBatis+easyUI整合基础篇(一)项目简述及技术选型介绍

    作者:13GitHub:https://github.com/ZHENFENG13版权声明:本文为原创文章,未经允许不得转载. 萌芽阶段 很久之前就开始打算整理一下自己的技术博客了,由于各种原因(借口 ...

  4. maven+springmvc+easyui+fastjson+pagehelper

    1.maven配置 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www ...

  5. SpringMVC(十三):SpringMVC 与fastjson集成

    1)fastjson jar包下载地址:https://sourceforge.net/projects/fastjson/下载完成后需要把jar包拷贝到WEB-INF/lib文件夹中.2)使用pom ...

  6. springmvc与fastjson的整合,注解@RequestBody的使用

    项目内容用的是jetty框架,传输数据格式是json格式,有一天我心血来潮,把项目又搭建了一次,完了,卡在了数据传输的格式上,明明原来框架直接用fastjson,但是我用就是不对,总是报fastjso ...

  7. SpringMVC整合fastjson、easyui 乱码问题

    一.框架版本 SpringMVC:3.1.1.RELEASE fastjson:1.2.7 easyui :1.4.5 二.乱码现象    Action中使用@ResponseBody返回Json数据 ...

  8. SpringMVC使用fastjson自定义Converter支持返回jsonp格式(转)

    import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import c ...

  9. 描述下fastJSON,jackson等等的技术

    ①Jackson:依赖的jar包较少,简单易用性能高,更新速度也比较快,但是对于复杂类型的json转换bean会出 现问题,一些集合Map,List的转换出现问题,对于复杂类型的bean转换Json, ...

随机推荐

  1. C语言生成32位和64位随机数算法

    C语言生成32位和64位随机数算法 /** * randstd.h * * Standard definitions and types, Bob Jenkins * * 2015-01-19: re ...

  2. 【linux学习笔记之一】linux系统目录结构以及常用系统命令

    序 ???这破笔记也要序?? 昨天开始学linux,做好笔记以备日后翻阅 Linux系统目录结构图 bin  --主要用于存放二进制文件(如:命令文件) boot--引导目录 dev  --设备目录 ...

  3. Jamon

    1.Jamon java 模版引擎 eclipse 插件   http://www.jamon.org/eclipse/updates 2.Jamon 官方网站 http://www.jamon.or ...

  4. wince6.0 编译报错:"error C2220: warning treated as error - no 'object' file generated"的解决办法

    内容提要:wince6.0编译报错:"error C2220: warning treated as error - no 'object' file generated" 原因是 ...

  5. 猴子吃桃问题---C实现

    原题:猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了了一个.第二天早上又将剩下的桃子吃掉一半,又多吃了一个.以后每一天早上都吃前一天剩下桃子的一半零一个.到第十天早上想再吃时,发现 只剩下 ...

  6. 【Nginx】下载,请求限速,根据URL参数限速

    这个场景是限制单个连接的下载速度,还有限制单个IP的连接数,或者单位时间内的请求数,实验环境 nginx1.9.x. 小例子为主,具体的细节请多看文档. 限制下载速度 location /downlo ...

  7. 【6】-BAT面试之操作系统内存详解

    本文主要参考两篇博客,读后整理出来,以供大家阅读,链接如下: http://blog.jobbole.com/95499/?hmsr=toutiao.io&utm_medium=toutiao ...

  8. Kubernetes如何支持有状态服务的部署?

    作者:Jack47 转载请保留作者和原文出处 PS:如果喜欢我写的文章,欢迎关注我的微信公众账号程序员杰克,两边的文章会同步,也可以添加我的RSS订阅源. Kubernetes对无状态服务有完善的支持 ...

  9. dsd

    nodeName属性 nodeName属性规定节点的名称 nodeName是只读的 元素节点的nodeName与属性名相同 属性节点的nodeName与属性名相同 文本节点nodeName始终是#te ...

  10. python的logging模块之读取yaml配置文件。

    python的logging模块是用来记录应用程序的日志的.关于logging模块的介绍,我这里不赘述,请参见其他资料.这里主要讲讲如何来读取yaml配置文件进行定制化的日志输出. python要读取 ...