记一次因为fastjson替换为gson是引发的血案

记一次因为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();
        }
    }

}

以上就是本次生产事故的主要处理代码,虽然问题最后解决了,但是经过本次总结出一个结论,就是看起来简单的事情有时候并不简单,以及修改上线的项目时需要做好对旧数据的兼容。希望能对大家有所帮助。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值