基于GSON实现单例和自定义日期输入与输出

上篇写道由于Fastjson漏洞问题导致系统频繁升级之后基于Jackson封装了一个支持多样化日期输出与输入的JSON转换工具,但实际在后面的使用中由于系统架构较老与Spring自带Jackson冲突较为严重,无奈之下只能放弃使用GSON来进行JSON转换。

GSON在使用中的问题

GSON本身使用是以new形式出现的,在实际使用中并不友好,在应对实际场景中有以下几个问题:
1、对于大并发来说频繁new对象也会造成一些内存占用。
2、对于date转换是采用DateFormat进行转换同时为了防止并发时产生问题使用了Synchronized锁,而且由于date输入与输出格式在new Gson()时已经确定对于有其它格式需求的功能来说只能重新进行new对象。
3、虽然可以针对通用场景进行封装工具类,但还是new的到处都是。

我们的解决方案

我们基于依照Jackson的思路,通过Gson提供的TypeAdapter插件用来封装我们自己的转换插件,并且提供兼容多种输入格式,与自定义输出格式。

自定义转换器

我们的自定义转换器在接收传入的字符串时,会兼容多种数据格式,例如我们常用的/与-分隔的日期,少数情况下使用点分隔的日期。

package com.ff.tools.json;

import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Description
 * @Author zhaoqiang
 * @Date 2020/8/15 4:32 下午
 * @Version
 */
public class CustomDateTypeAdapter extends TypeAdapter<Date>{

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomDateTypeAdapter.class);

    /**
     * 严格的格式校验
     * YYYY-MM-dd HH:mm:ss.ZZZ
     */
    private static Pattern ZH_DEFAULT_YYYY_MM_DD_HH_MM_SS_SSS = Pattern.compile("\\d\\d\\d\\d[-/.]\\d\\d[-/.]\\d\\d \\d\\d[:]\\d\\d(?:[:]\\d\\d)?(?:[\\.]\\d{1,3})?");

    private static Pattern TIMESTAMP=Pattern.compile("^\\d+$");
    /**
     * 中文默认格式化格式
     */
    private final static String ZH_DEFAULT_FORMAT = "yyyy{-/.}MM{-/.}dd HH:mm:ss";
    /**
     * 年月日格式的字符串长度
     */
    private final static int YYYY_MM_DD_LENGTH=10;
    /**
     * 年月日时分长度判断
     */
    private final static int YYYY_MM_DD_HH_MM_SS_LENGTH=16;

    /**
     * 带毫秒数位数判断
     */
    private final static int MILLI_SECOND_LEN = 19;

    /**
     * time split sign
     */
    private final static char TIME_SECOND_SPLIT_SIGN = ':';

    private final static char TIME_MILLISECOND_SPLIT_SIGN = '.';

    /**
     * 东八区时区
     */
    private static TimeZone ZH_TIMEZONE = TimeZone.getTimeZone("GMT+8");
    /**
     * 自定义输出日期格式
     */
    private String writeDateFormat;
    private final ThreadLocal<DateFormat> dateFormatThreadLocal = new ThreadLocal<DateFormat>(){
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat(writeDateFormat);
        }
    };

    public CustomDateTypeAdapter() {
    }

    public CustomDateTypeAdapter(String writeDateFormat) {
        this.writeDateFormat = writeDateFormat;
    }
    @Override
    public void write(JsonWriter out, Date value) throws IOException {
        if (value == null) {
            out.nullValue();
            return;
        }
        if (Objects.isNull(writeDateFormat) || writeDateFormat.length() == 0) {
            out.value(value.getTime());
        }else{
            out.value(dateFormatThreadLocal.get().format(value));
        }
    }


    @Override
    public Date read(JsonReader in) throws IOException {
    	if(in.peek()== JsonToken.NULL){
    		in.nextNull();
    		return null;
    	}
        String dateStr = in.nextString();
        final int totalLen = dateStr.length();
        if(TIMESTAMP.matcher(dateStr).matches()){
            long timestamp = Long.parseLong(dateStr);
            return new Date(timestamp);
        }
        if (totalLen > YYYY_MM_DD_LENGTH) {
            Matcher m = ZH_DEFAULT_YYYY_MM_DD_HH_MM_SS_SSS.matcher(dateStr);
            if (!m.matches()) {
                LOGGER.error(String.format("Cannot parse date \"%s\": while it seems to fit format '%s', parsing fails", dateStr, ZH_DEFAULT_FORMAT));
                return null;
            }
            TimeZone tz = ZH_TIMEZONE;
            Calendar cal = Calendar.getInstance(tz);
            int year = _parse4D1(dateStr, 0);
            int month = _parse2D1(dateStr, 5)-1;
            int day = _parse2D1(dateStr, 8);

            // So: 10 chars for date, then `T`, so starts at 11
            int hour = _parse2D1(dateStr, 11);
            int minute = _parse2D1(dateStr, 14);

            // Seconds are actually optional... so
            int seconds = 0;
            if ((totalLen > YYYY_MM_DD_HH_MM_SS_LENGTH) && dateStr.charAt(YYYY_MM_DD_HH_MM_SS_LENGTH) == TIME_SECOND_SPLIT_SIGN) {
                seconds = _parse2D1(dateStr, 17);
            }
            int millisecond = 0;
            //如果长度大于毫秒数,且带有毫秒点
            if ((totalLen > MILLI_SECOND_LEN) && dateStr.charAt(MILLI_SECOND_LEN) == TIME_MILLISECOND_SPLIT_SIGN) {
                millisecond = _parse3D1(dateStr, 20);
            }
            cal.set(year, month, day, hour, minute, seconds);
            cal.set(Calendar.MILLISECOND, millisecond);
            return cal.getTime();

        }
        return null;
    }

    /**
     * 获取四位数字
     * @param str
     * @param index
     * @return
     */
    private static int _parse4D1(String str, int index) {
        return (1000 * (str.charAt(index) - '0'))
                + (100 * (str.charAt(index+1) - '0'))
                + (10 * (str.charAt(index+2) - '0'))
                + (str.charAt(index+3) - '0');
    }

    /**
     * 获取二位数字
     * @param str
     * @param index
     * @return
     */
    private static int _parse2D1(String str, int index) {
        int ch = 10 * (str.charAt(index) - '0');
        int ch1 = str.charAt(index + 1) - '0';
        return ch + ch1;
    }

    /**
     * 获取二位数字
     * @param str
     * @param index
     * @return
     */
    private static int _parse3D1(String str, int index) {
        return (100 * (str.charAt(index) - '0'))
                +(10 * (str.charAt(index+1) - '0'))
                + (str.charAt(index+2) - '0');
    }
}

创建适配器工厂

Gson的TypeAdapter都是通过工厂去创建,所以要实现TypeAdapterFactory创建接口

package com.ff.tools.json;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;

/**
 * @Description
 * @Author zhaoqiang
 * @Date 2020/8/15 4:32 下午
 * @Version
 */
public class CustomDateTypeAdapterFactory implements TypeAdapterFactory{

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomDateTypeAdapterFactory.class);

    /**
     * 自定义输出日期格式
     */
    private String writeDateFormat;

    public CustomDateTypeAdapterFactory() {
    }

    public CustomDateTypeAdapterFactory(String writeDateFormat) {
        this.writeDateFormat = writeDateFormat;
    }

    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        return type.getRawType() == Date.class ? (TypeAdapter<T>) new CustomDateTypeAdapter(writeDateFormat) : null;
    }

}
主角亮相

一个简单的单例类,由于TypeAdapter并没有提供类似于Jackson与Fastjson那种由对象转换JSON时可以传入指定Date格式的参数,所以我们简单以一个DateFormatMap缓存的形式来实现有自定义输出格式的需求。

package com.ff.tools.json;

import com.google.gson.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Description
 * @Author zhaoqiang
 * @Date 2020/8/15 3:25 下午
 * @Version
 */
public class JsonUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(JsonUtils.class);
    private static final Gson read = new GsonBuilder().registerTypeAdapterFactory(new CustomDateTypeAdapterFactory()).create();
    private static volatile Map<String, Gson> withDateFormat = new HashMap<>();

    /**
     * 对象转换json
     *
     * @param o
     * @return {@link String}
     * @Author zhaoqiang
     * @Date 2019/10/21 12:24 上午
     */
    public static String toJsonString(Object o) {
        if (o == null) {
            return null;
        }
        return read.toJson(o);
    }

    /**
     * 对象转换json
     * 自定义时间格式转换
     *
     * @param o
     * @param dateFormat
     * @return {@link String}
     * @Author zhaoqiang
     * @Date 2019/10/21 12:25 上午
     */
    public static String toJsonStringWithDateFormat(Object o, String dateFormat) {
        if (o == null) {
            return null;
        }
        Gson gson = withDateFormat.get(dateFormat);
        if (gson == null) {
            String lock = "JSON_" + dateFormat;
            synchronized (lock.intern()) {
                gson = withDateFormat.get(dateFormat);
                if (gson == null) {
                    gson = read.newBuilder().registerTypeAdapterFactory(new CustomDateTypeAdapterFactory(dateFormat)).create();
                    withDateFormat.put(dateFormat, gson);
                }
            }
        }
        return gson.toJson(o);
    }

    /**
     * json转换成对象
     *
     * @param json
     * @param clazz
     * @return {@link T}
     * @Author zhaoqiang
     * @Date 2019/10/21 12:25 上午
     */
    public static <T> T parseObject(String json, Class<T> clazz) {
        try {
            return read.fromJson(json, clazz);
        } catch (JsonSyntaxException e) {
            LOGGER.error("格式化JSON字符串异常", e);
        }
        return null;
    }

    /**
     * json转换map
     *
     * @param json
     * @return
     */
    public static Map<String, Object> parseMap(String json) {
        if (json == null || json.length() == 0) {
            return null;
        }
        try {
            JsonElement element = JsonParser.parseString(json);
            JsonObject jsonObject = element.getAsJsonObject();
            return jsonObject.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        } catch (JsonSyntaxException e) {
            LOGGER.error("格式化JSON字符串异常", e);
        }
        return null;
    }

    /**
     * json转换list集合
     *
     * @param json
     * @return {@link String}
     * @Author zhaoqiang
     * @Date 2019/10/21 12:26 上午
     */
    public static <T> List<T> parseArray(String json, Class<T> clazz) {
        if (json == null || json.length() == 0) {
            return null;
        }
        try {
            Type type = new ParameterizedTypeImpl(List.class, new Type[]{clazz});
            return read.fromJson(json, type);
        } catch (JsonSyntaxException e) {
            LOGGER.error("格式化JSON字符串异常", e);
        }
        return null;
    }

    public static class ParameterizedTypeImpl implements ParameterizedType {

        private Class raw;
        private Type[] types;

        public ParameterizedTypeImpl(Class raw, Type[] types) {
            this.raw = raw;
            this.types = types;
        }

        @Override
        public Type[] getActualTypeArguments() {
            return types;
        }

        @Override
        public Type getRawType() {
            return raw;
        }

        @Override
        public Type getOwnerType() {
            return raw.getDeclaringClass();
        }
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值