FastJson自定义全局序列化方式与源码解析

FastJson自定义全局序列化方式与源码解析


前言

在进行序列化时,我们会遇到Long型序列化到前端出现精度丢失的问题。这种情况通常可以通过直接在属性上使用@JSONField指定string类型的序列化来解决,但是我们也可以通过自定义全局配置来解决该问题,本文通过分析源码一步步调整,可以帮助像我一样的菜狗在遇到百度查不到的问题时提供一种解决思路。


一、fastjson是什么?

fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。

二、开搞

首先我们创建一个配置类实现WebMvcConfigurer或继承WebMvcConfigurationSupport:

@EnableWebMvc
@Configuration
public class WebJsonConfig implements WebMvcConfigurer {
}

这里解释一下@EnableWebMvc这个注解,如果我们不加这个注解,容器中依旧会加载springboot的默认自动配置。
我们可以从源码来看一下,@EnableWebMvc这个注解 上面导入了一个webmvc的代理配置类DelegatingWebMvcConfiguration.class
在这里插入图片描述
进入这个类,我们可以看到该类继承了WebMvcConfigurationSupport 并使用代理配置组件重写了所有的配置,方法,
在这里插入图片描述
通过阅读springboot的自动配置我们可以发现springboot会在当前容器中不存在WebMvcConfigurationSupport的bean时才将会装载默认配置bean,由于我们的自定义的配置类加上了@EnableWebMvc注解,此时容器中已经存在WebMvcConfigurationSupport的子类bean了,于是springboot默认的自动配置便不会装载,系统就会使用我们的自定义配置类。
在这里插入图片描述

接下来我们进入WebMvcConfigurer
在这里插入图片描述
可以看到原来web.xml的基本都由这个类来承担了,我们可以重写里面的方法来对拦截器,视图解析器,视图控制器,全局异常处理进行自定义配置。我们今天设置序列化的方法用到的是configureMessageConverters的这个方法。
在这里插入图片描述
接下来我们准备重写该方法,从该方法的参数中我们可以看到入参是一个 HttpMessageConverter的集合,并且该方法是一个void方法,那么我们要做的操作一定对该集合进行操作。
这里我们通过阅读springboot的源码可以知道,只要将我们自定义的消息转换add到集合中就可以了。
在这里插入图片描述
那么FastJson已经给我们封装好了一个消息转换类,我们只需要创建这么一个对象,并对该对象进行我们的自定义配置后,添加到converters集合中就可以了。
于是我们的方法重写第一步开始了:

@EnableWebMvc
@Configuration
public class WebJsonConfig implements WebMvcConfigurer {
@Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
		//待完善
		//
		//
		converters.add(fastConverter);
	}
}

接下来我们需要对fastjson的消息转换进行我们自己的配置

点进源码,我们可以看到这里存在一个一个方法,刚好就是用来设置fastjson的配置,但是需要我们传一个FastJsonConfig的对象。
在这里插入图片描述
于是我们在代码中新建一个fastjsonConfig对象,有了这个对象后我们需要进行配置
点击这个类,我们可以看到一个设置序列化配置的方法。
在这里插入图片描述
点进SerializeConfig对象 我们可以看到该类采用的是单例设计模式,我们通过globalInstance拿到这个实例。
在这里插入图片描述
继续跟代码我们可以发现,该实例的在初始化时 初始化了常见的数据类型的序列化方法,将每种类型及对应的序列化方法PUT进阿里自定义的IdentityHashMap数据结构中,我们要改的Long.class也在其中。

在这里插入图片描述
点击对应的序列方法我们可以看到Long型在序列化时会将对象强转成Long然后取其值,这时我们已经找到目标了,就是去修改这个自带的序列化方法

继续看源码可以发现,阿里的这个自定义的map在PUT值时如果是已经存在的键会直接将新值覆盖旧值
在这里插入图片描述
这样一来我们的问题就解决了,只需要创建一个我们自己的序列化器实现ObjectSerializer接口后重写里面的write方法,并将这个序列化器以Long.class的键值PUT进去就可以完成修改了。

本文中其实有一个需求,是将Long类型存入list后序列化list,这时即便put了我们自定义的Long序列化,依然不能将list的中的Long型数据转换成字符型,于是我们继续跟进源码,由于我们这里使用的是arraylist,对应会找到ListSerializer,如下图
在这里插入图片描述
通过源码我们可以发现,在对list里面的一个个元素进行序列号时如果判断类型为Long依旧是强转为Long后取值,问题找到了,解决方法依旧是和上面一样重写,然后put进来,但是由于ListSerializer是final类
无法继承重写,于是笔者找到了map中默认添加的linkedlist对应的序列化类 CollectionCodec,继承该类重写write方法,put进配置类的map,大工告成。
在这里插入图片描述

下面是代码实现:

@EnableWebMvc
@Configuration
public class WebJsonConfig implements WebMvcConfigurer {






    SimpleDateFormat sdf= new   SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private ObjectSerializer objectSerializer = (serializer, object, fieldName, fieldType, features) -> {

        SerializeWriter out = serializer.getWriter();
        if (object instanceof Date) {
            out.writeString(object != null ? sdf.format((Date) object) : "");
        }
        if (object instanceof Long) {
            out.writeString(object.toString());
        }

    };




    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 1、需要先定义一个·convert转换消息的对象;
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
      //  FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();

        //升级最新版本需加=============================================================
        List<MediaType> supportedMediaTypes = new ArrayList<>();
        supportedMediaTypes.add(MediaType.APPLICATION_JSON);
        supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        supportedMediaTypes.add(MediaType.APPLICATION_ATOM_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
        supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
        supportedMediaTypes.add(MediaType.APPLICATION_PDF);
        supportedMediaTypes.add(MediaType.APPLICATION_RSS_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_XHTML_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_XML);
        supportedMediaTypes.add(MediaType.IMAGE_GIF);
        supportedMediaTypes.add(MediaType.IMAGE_JPEG);
        supportedMediaTypes.add(MediaType.IMAGE_PNG);
        supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM);
        supportedMediaTypes.add(MediaType.TEXT_HTML);
        supportedMediaTypes.add(MediaType.TEXT_MARKDOWN);
        supportedMediaTypes.add(MediaType.TEXT_PLAIN);
        supportedMediaTypes.add(MediaType.TEXT_XML);
        fastConverter.setSupportedMediaTypes(supportedMediaTypes);
        // 2、添加fastjson的配置信息,比如 是否要格式化返回json数据
        FastJsonConfig fastJsonConfig = new FastJsonConfig();

        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        SerializeConfig serializeConfig = SerializeConfig.globalInstance;
        serializeConfig.put(Long.class, objectSerializer);
        serializeConfig.put(Date.class, objectSerializer);
        serializeConfig.put(ArrayList.class,new myclass());
        fastJsonConfig.setSerializeConfig(serializeConfig);
        fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");


        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,
                SerializerFeature.DisableCircularReferenceDetect,
                SerializerFeature.WriteMapNullValue);
        // 3、在convert中添加配置信息.
        fastConverter.setFastJsonConfig(fastJsonConfig);
        // 4、将convert添加到converters当中.
        converters.add(fastConverter);

    }
}

    class myclass extends CollectionCodec{

        @Override
        public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
            SerializeWriter out = serializer.out;

            if (object == null) {
                out.writeNull(SerializerFeature.WriteNullListAsEmpty);
                return;
            }

            Type elementType = null;
            if (out.isEnabled(SerializerFeature.WriteClassName)
                    || SerializerFeature.isEnabled(features, SerializerFeature.WriteClassName))
            {
                elementType = TypeUtils.getCollectionItemType(fieldType);
            }

            Collection<?> collection = (Collection<?>) object;

            SerialContext context = serializer.getContext();
            serializer.setContext(context, object, fieldName, 0);

            if (out.isEnabled(SerializerFeature.WriteClassName)) {
                if (HashSet.class == collection.getClass()) {
                    out.append("Set");
                } else if (TreeSet.class == collection.getClass()) {
                    out.append("TreeSet");
                }
            }

            try {
                int i = 0;
                out.append('[');
                for (Object item : collection) {

                    if (i++ != 0) {
                        out.append(',');
                    }

                    if (item == null) {
                        out.writeNull();
                        continue;
                    }

                    Class<?> clazz = item.getClass();

                    if (clazz == Integer.class) {
                        out.writeInt(((Integer) item).intValue());
                        continue;
                    }

//                    if (clazz == Long.class) {
//                        out.writeLong(((Long) item).longValue());
//
//                        if (out.isEnabled(SerializerFeature.WriteClassName)) {
//                            out.write('L');
//                        }
//                        continue;
//                    }

                    ObjectSerializer itemSerializer = serializer.getObjectWriter(clazz);
                    if (SerializerFeature.isEnabled(features, SerializerFeature.WriteClassName)
                            && itemSerializer instanceof JavaBeanSerializer) {
                        JavaBeanSerializer javaBeanSerializer = (JavaBeanSerializer) itemSerializer;
                        javaBeanSerializer.writeNoneASM(serializer, item, i - 1, elementType, features);
                    } else {
                        itemSerializer.write(serializer, item, i - 1, elementType, features);
                    }
                }
                out.append(']');
            } finally {
                serializer.setContext(context); ;
            }
        }

    }

这里由于date类型的转换也没有默认添加,所以笔者也添加了date类型的序列化转换。

在这里插入图片描述
可以看到Long和date 都按照我们的规定转换了
在这里插入图片描述

总结

我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值