上篇写道由于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();
}
}
}