记一次因为fastjson替换为gson是引发的血案
有一次客户提了一个要求替换项目中的fastjson具体原因是fastjson有很多漏洞,客户要求用谷歌的gson来替换,一开始没多想以为很简单就直接替换对应的方法json的序列化和反序列化等,但是把替换的包给客户后,生产环境出故障了,接着把我们叫到现场排查问题,经过看日志最终发现是因为gson和fastjson序列化对象时对日期的处理方式不一样,fastjson默认是转换为时间戳(unix)格式,gson则是Dec 8, 2017 3:33:08 PM,而生产环境因为存在旧数据导致反序列化失败导致系统无法使用,这个锅算我的,没有考虑到旧数据的问题更没有考虑到不同json对于日期的不同处理方式!!!
没办法问题还是要处理,既然知道问题的所在那就做针对性的处理了,主要是做好兼容处理,通过自定义gson日期序列化器实现gson支持时间戳(unix)和gson默认的日期格式,解决办法如下:
gson工具类(序列化和反序列化,把对象转json字符串和把json字符串转对象)
package com.szitrus.smp.util;
import com.google.gson.*;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.bind.CollectionTypeAdapterFactory;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.szitrus.smp.core.domain.Signature;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.Map;
/**
* Gson工具类 2020-03-26<br/>
* 1,把json转换成Object<br/>
* 2,把Object转换成json<br/>
* 3,该方法主要功能是将json字符串转换成指定类型的对象<br/>
*/
public class GsonUtils {
/**
* Gson对象
*/
private static Gson gson = null;
/**
* JsonParser对象
*/
private static JsonParser jsonParser = null;
/**
* 自定义TypeAdapter ,null对象将被解析成空字符串
*/
static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
@Override
public String read(JsonReader reader) {
try {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return "";//原先是返回Null,这里改为返回空字符串
}
return reader.nextString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
@Override
public void write(JsonWriter writer, String value) {
try {
if (value == null) {
writer.nullValue();
return;
}
writer.value(value);
} catch (Exception e) {
e.printStackTrace();
}
}
};
/**
* 自定义adapter,解决由于数据类型为Int,实际传过来的值为Float,导致解析出错的问题
* 目前的解决方案为将所有Int类型当成Double解析,再强制转换为Int
*/
static final TypeAdapter<Number> INTEGER = new TypeAdapter<Number>() {
@Override
public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return 0;
}
try {
double i = in.nextDouble();
return (int) i;
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
@Override
public void write(JsonWriter out, Number value) throws IOException {
out.value(value);
}
};
static {
GsonBuilder gsonBulder = new GsonBuilder();
gsonBulder.registerTypeAdapter(String.class, STRING); //所有String类型null替换为字符串“”
gsonBulder.registerTypeAdapter(int.class, INTEGER); //int类型对float做兼容
gsonBulder.registerTypeAdapter(java.util.Date.class, new GsonDateDeSerializer());
//通过反射获取instanceCreators属性
try {
Class builder = (Class) gsonBulder.getClass();
Field f = builder.getDeclaredField("instanceCreators");
f.setAccessible(true);
Map<Type, InstanceCreator<?>> val = (Map<Type, InstanceCreator<?>>) f.get(gsonBulder);//得到此属性的值
//注册数组的处理器
gsonBulder.registerTypeAdapterFactory(new CollectionTypeAdapterFactory(new ConstructorConstructor(val)));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
gson = gsonBulder.create();
jsonParser = new JsonParser();
}
/**
* 1,该方法主要功能是将json字符串转换成JsonObject
*
* @param json json字符串
* @return 解析后的JsonObject
* @throws JsonSyntaxException 如果解析中出了问题,或者是json不完整都会抛出这个异常信息
*/
public static JsonObject parseJson(String json) throws JsonSyntaxException {
return gson.fromJson(json, JsonObject.class);
}
/**
* 2,该方法主要功能是将json字符串转换成JsonArray
*
* @param json json字符串
* @return 解析后的JsonArray
* @throws JsonSyntaxException 如果解析中出了问题,或者是json不完整都会抛出这个异常信息
*/
public static JsonArray parseJsonArray(String json) throws JsonSyntaxException {
return jsonParser.parse(json).getAsJsonArray();
}
/**
* 3,该方法主要功能是将json字符串转换成java对象
*
* @param json json字符串
* @return 解析后的java对象
* @throws JsonSyntaxException 如果解析中出了问题,或者是json不完整都会抛出这个异常信息
*/
public static <T> T parseObject(String json, Class<T> clas) throws JsonSyntaxException {
return gson.fromJson(json, clas);
}
/**
* 4,该方法主要功能是将Java类对象转换成json字符串
*
* @param obj Java对象
* @return json字符串
*/
public static String toJson(Object obj) {
return gson.toJson(obj);
}
}
gson自定义日期序列化解析器(主要):
package com.szitrus.smp.util;
import cn.hutool.core.util.StrUtil;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.Locale;
/**
* Gson 自定义日期解析器
*/
public class GsonDateDeSerializer implements JsonDeserializer<Date> {
private static final Logger logger = LoggerFactory.getLogger(GsonDateDeSerializer.class);
@Override
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
String j = json.getAsJsonPrimitive().getAsString();
return parseDate(j);
} catch (JsonParseException e) {
throw new JsonParseException(e.getMessage(), e);
}
}
private Date parseDate(String dateString) throws JsonParseException {
DateFormat format = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, Locale.US);
if (StringUtils.isNotBlank(dateString)) {
try {
return format.parse(dateString);
} catch (ParseException pe) {
try {
return new Date(Long.parseLong(dateString));
} catch (Exception e) {
logger.error("不支持的日期格式:{}", dateString, e);
throw new JsonParseException(StrUtil.format("不支持的日期格式:{}", dateString), e);
}
}
} else {
return null;
}
}
public static void main(String[] args) {
DateFormat format = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, Locale.US);
String s = "12312";
try {
System.out.println(new Date());
} catch (Exception e) {
e.printStackTrace();
}
}
}
以上就是本次生产事故的主要处理代码,虽然问题最后解决了,但是经过本次总结出一个结论,就是看起来简单的事情有时候并不简单,以及修改上线的项目时需要做好对旧数据的兼容。希望能对大家有所帮助。