目录

前言

自定义序列化策略是如何实现的

我的排查过程

解决方案

小结


前言

我们公司的框架对接口返回的java对象转换为字符串的序列化策略上进行了定制。如:当String类型属性值为null时,会转换为空字符串返回、当List类型为nul时返回长度为0的空数组、当Boolean类型为null时返回false、对Bigdecimal类型的属性值,默认保留2位小数点

关于最后一点,当业务需要指定其他精度时,可以在成员属性上添加@JSONField注解来指定保留小数位数。

/**
     * 保留小数点后6位
     */
    @ApiModelProperty("经度")
    @JSONField(format = "#0.000000")
    private BigDecimal longitude;

但是经实验后发现,该注解在微服务A下生效,在微服务B下无效,即 始终返回2位小数。因此尝试定位原因并修复。

自定义序列化策略是如何实现的

注:以下代码、图片均为个人工程的模拟重现,去除了一些无关代码以便于理解。

根据框架配置可以发现在WebMvcConfiguration.java中重写了 configureMessageConverters(List<HttpMessageConverter<?>> converters) 方法,并引入了 CustomBigDecimalCodec.java 对BigDecimal属性的序列化策略进行定制。

mogodb Java Document序列化_序列化

CustomBigDecimalCodec.java 是一个自定义编解码器,在参数传入、返回时进行 “字符串和BigDecimal属性”之间的转换策略。

它继承了fastjson(version: 1.2.70)的BigDecimalCodec.java并重写了write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features)方法,以实现默认情况下返回2位小数。

同时实现了ContextObjectSerializer.java接口并实现了write(JSONSerializer serializer, Object object, BeanContext context)方法,以实现在有@JSONField(format="xxx")注解时返回自定义精度。

mogodb Java Document序列化_序列化_02

 断点调试发现,微服务A的接口返回BigDecimal属性时,会进入CustomBigDecimalCodec.java类(带@JSONField注解进入第一个方法,不带进入第二个方法)。但是微服务B相同场景下,不会进入CustomBigDecimalCodec.java类,我以此为切入点,进行问题排查。

我的排查过程

我在2个微服务下均调用相同接口,返回包含BigDecimal类型属性的对象,调试对比。

微服务A:

在CustomBigDecimalCodec#write 处打断点,并观察方法调用栈,发现是由com.alibaba.fastjson.serializer.JavaBeanSerializer的470行进入的

mogodb Java Document序列化_java_03

mogodb Java Document序列化_微服务_04

 微服务B:

而在微服务B下,并不会进入470行,发现是因为在第307行时,BigDecimal类型属性的值发生了改变,被格式化为了2位小数(而不是像微服务A一样在470行进入CustomBigDecimalCodec.java被格式化)

mogodb Java Document序列化_序列化_05

mogodb Java Document序列化_序列化_06

进入307行的this.processValue方法,发现SerializeFilterable.java的233行,在butler服务下,jsonBeanDeser.valueFilters里存在一个叫做BigDecimalValueFilter的过滤器

mogodb Java Document序列化_序列化_07

这个BigDecimalValueFilter 过滤器实现了ValueFilter接口的process方法,入参为待序列化对象实例、待序列化属性名、待序列化属性值。在方法实现中,它将BigDecimal类型属性值格式化为2位小数(无论是否有@JSONField注解)

mogodb Java Document序列化_序列化_08

调试至此,基本上可以得出结论:在微服务B下,存在一个特殊的过滤器BigDecimalValueFilter.java,它的优先级高于CustomBigDecimalCodec.java,且不支持@JSONField注解自定义格式化。导致CustomBigDecimalCodec编解码器失效,即 @JSONField失效。

搜索全局代码,发现在微服务B的WebMvcConfiguration.java 的 configureMessageConverters(List<HttpMessageConverter<?>> converters) 方法下,配置了BigDecimalValueFilter,导致问题出现。

mogodb Java Document序列化_开发语言_09

解决方案

1、在WebMvcConfiguration.java中删除BigDecimalValueFilter配置。因为他会覆盖CustomBigDecimalCodec编解码器,导致@JSONField注解自定义BigDecimal精度失效。且CustomBigDecimalCodec编解码器的功能要强于BigDecimalValueFilter过滤器

2、对BigDecimalValueFilter进行改造,使其支持@JSONField注解。刚才提到它实现了process方法,入参中有待序列化对象实例,可以由此向上拿到Class对象,进一步拿到类属性以及属性注解,从而支持@JSONField注解(甚至任何自定义注解

小结

完成Fastjson序列化的源码追踪后,再对比框架中的配置,可以得到如下结论。

1、在 FastJsonHttpMessageConverter.fastJsonConfig.serializeConfig 中,我们可以按照数据类型配置自定义编解码(序列化、反序列化)策略。如:返回BigDecimal类型属性时,固定保留2位精度或自定义精度...

2、在 FastJsonHttpMessageConverter.fastJsonConfig.serializeFilters 中,我们可以配置多个过滤器,它同样能实现上述功能,自由度更高(可拿到待序列化对象实例),且优先级高于自定义编解码器。若过滤器对待序列化属性值造成了变化,则自定义编解码器会对该属性失效

附一个流程图

mogodb Java Document序列化_java_10