【SpringMVC】從Fastjson遷移到Jackson,以及對技術選型的反思

爲何要換掉fastjson

直接緣由是fastjson沒法支持註解形式的自定義序列化和反序列化,雖然其Github上的Wiki上說明是支持的。可是實測結果代表:Test類的序列化被fastjson的ASMFactory生成字節碼形式的序列化類代理,序列化的邏輯依然爲原生而不是自定義的XXX.classhtml

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

在官方Wiki上沒有找到關閉ASM特性的方法,本身嘗試在構造FastJsonHttpMessageConverter時用Feature.DisableASM也沒有效果。java

<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是一種合理的選擇。spring

關於技術選型的一點反思

根據網絡上公開的資料比較獲得下表。看起來不如不使用須要額外配置的高級特性,fastjson有官方的中文支持,學習難度也很低。編程

fastjson jackson
上手容易度 容易 中等
高級特性支持 中等 豐富
官方文檔、Example支持 中文、少、無體系 英文、較多
非官方資料 高級特性用例少、stackoverflow上問題少 中文資料較多

可是實際使用下來,fastjson的高級特性支持不完善、存在bug、得不到英文社區的支持,致使本身在業務實際應用時反而須要較多的時間去調試定位類庫自己的問題。所以技術選型的時候要注意不能貪圖容易上手,最好考慮其社區活躍度和網絡上各類渠道的資料豐富程度,由於這在必定程度上展現了類庫的健壯性。json

如何兼容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中。app

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

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

在標準Json規範中,若是嘗試反序列化一個Json字符串爲指定的POJO,並且字符串中有一個Field在POJO中不存在,應該拋出錯誤。對於這種狀況,fastjson默認是跳過,jackson默認是停止。ide

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
相關文章
相關標籤/搜索