赞
踩
在进行序列化时,我们会遇到Long型序列化到前端出现精度丢失的问题。这种情况通常可以通过直接在属性上使用@JSONField指定string类型的序列化来解决,但是我们也可以通过自定义全局配置来解决该问题,本文通过分析源码一步步调整,可以帮助像我一样的菜狗在遇到百度查不到的问题时提供一种解决思路。
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 都按照我们的规定转换了
我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗我是菜狗。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。