后端使用Spring接收参数,用@ResquestBody接收请求入参,前端传参一日期类型的参数为“2022-12-30”,后端接收到以后打印的值为“2022-12-30 08:00:00”,后端比前端提前8小时。
后端使用@RestController,给前端返回时间类型的参数时,前端接收到的时间比后端滞后8小时。
这种情况下,后端认为前端传参或者后端返回给前端的参数都是JSON格式,所以使用解析JSON的。后端在解析前端传参时,使用的参数转换器是MappingJackson2HttpMessageConverter,默认时区是UTC,而我们一般的机器中的JVM 是东八区,两个时区不一样,导致这种情况下日期类型的转换会做一定的处理。故,本质上取决于处理时间类型的处理器使用的时区和JVM时区是否一致。跟踪代码,发现最终使用的是StdDateFormat类中的parse方法进行解析,代码如下:
关键类:
com.fasterxml.jackson.databind.util.StdDateFormat#parse(java.lang.String, java.text.ParsePosition)
public class StdDateFormat extends DateFormat {@Override
public Date parse(String dateStr) throws ParseException
{dateStr = dateStr.trim();
ParsePosition pos = new ParsePosition(0);
Date dt = _parseDate(dateStr, pos);
if (dt != null) {return dt;
}
StringBuilder sb = new StringBuilder();
for (String f : ALL_FORMATS) {if (sb.length() >0) {sb.append("\", \"");
} else {sb.append('"');
}
sb.append(f);
}
sb.append('"');
throw new ParseException
(String.format("Cannot parse date \"%s\": not compatible with any of standard forms (%s)",
dateStr, sb.toString()), pos.getErrorIndex());
}
protected Date _parseDate(String dateStr, ParsePosition pos) throws ParseException
{if (looksLikeISO8601(dateStr)) {// also includes "plain"
return parseAsISO8601(dateStr, pos);
}
// Also consider "stringified" simple time stamp
int i = dateStr.length();
while (--i >= 0) {char ch = dateStr.charAt(i);
if (ch< '0' || ch >'9') {// 07-Aug-2013, tatu: And [databind#267] points out that negative numbers should also work
if (i >0 || ch != '-') {break;
}
}
}
if ((i< 0)
// let's just assume negative numbers are fine (can't be RFC-1123 anyway); check length for positive
&& (dateStr.charAt(0) == '-' || NumberInput.inLongRange(dateStr, false))) {return _parseDateFromLong(dateStr, pos);
}
// Otherwise, fall back to using RFC 1123. NOTE: call will NOT throw, just returns `null`
return parseAsRFC1123(dateStr, pos);
}
protected Date parseAsISO8601(String dateStr, ParsePosition pos)
throws ParseException
{try {return _parseAsISO8601(dateStr, pos);
} catch (IllegalArgumentException e) {throw new ParseException(String.format("Cannot parse date \"%s\", problem: %s",
dateStr, e.getMessage()),
pos.getErrorIndex());
}
}
protected Date _parseAsISO8601(String dateStr, ParsePosition bogus)
throws IllegalArgumentException, ParseException
{final int totalLen = dateStr.length();
// actually, one short-cut: if we end with "Z", must be UTC
TimeZone tz = DEFAULT_TIMEZONE;
if ((_timezone != null) && ('Z' != dateStr.charAt(totalLen-1))) {tz = _timezone;
}
Calendar cal = _getCalendar(tz);
cal.clear();
String formatStr;
// 对于只有年月日的字符串,走这个分支
if (totalLen<= 10) {Matcher m = PATTERN_PLAIN.matcher(dateStr);
if (m.matches()) {int year = _parse4D(dateStr, 0);
int month = _parse2D(dateStr, 5)-1;
int day = _parse2D(dateStr, 8);
cal.set(year, month, day, 0, 0, 0);
cal.set(Calendar.MILLISECOND, 0);
return cal.getTime();
}
formatStr = DATE_FORMAT_STR_PLAIN;
} else {Matcher m = PATTERN_ISO8601.matcher(dateStr);
if (m.matches()) {// Important! START with optional time zone; otherwise Calendar will explode
int start = m.start(2);
int end = m.end(2);
int len = end-start;
if (len >1) {// 0 ->none, 1 ->'Z'
// NOTE: first char is sign; then 2 digits, then optional colon, optional 2 digits
int offsetSecs = _parse2D(dateStr, start+1) * 3600; // hours
if (len >= 5) {offsetSecs += _parse2D(dateStr, end-2) * 60; // minutes
}
if (dateStr.charAt(start) == '-') {offsetSecs *= -1000;
} else {offsetSecs *= 1000;
}
cal.set(Calendar.ZONE_OFFSET, offsetSecs);
// 23-Jun-2017, tatu: Not sure why, but this appears to be needed too:
cal.set(Calendar.DST_OFFSET, 0);
}
int year = _parse4D(dateStr, 0);
int month = _parse2D(dateStr, 5)-1;
int day = _parse2D(dateStr, 8);
// So: 10 chars for date, then `T`, so starts at 11
int hour = _parse2D(dateStr, 11);
int minute = _parse2D(dateStr, 14);
// Seconds are actually optional... so
int seconds;
if ((totalLen >16) && dateStr.charAt(16) == ':') {seconds = _parse2D(dateStr, 17);
} else {seconds = 0;
}
cal.set(year, month, day, hour, minute, seconds);
// Optional milliseconds
start = m.start(1) + 1;
end = m.end(1);
int msecs = 0;
if (start >= end) {// no fractional
cal.set(Calendar.MILLISECOND, 0);
} else {// first char is '.', but rest....
msecs = 0;
final int fractLen = end-start;
switch (fractLen) {default: // [databind#1745] Allow longer fractions... for now, cap at nanoseconds tho
if (fractLen >9) {// only allow up to nanos
throw new ParseException(String.format(
"Cannot parse date \"%s\": invalid fractional seconds '%s'; can use at most 9 digits",
dateStr, m.group(1).substring(1)
), start);
}
// fall through
case 3:
msecs += (dateStr.charAt(start+2) - '0');
case 2:
msecs += 10 * (dateStr.charAt(start+1) - '0');
case 1:
msecs += 100 * (dateStr.charAt(start) - '0');
break;
case 0:
break;
}
cal.set(Calendar.MILLISECOND, msecs);
}
return cal.getTime();
}
formatStr = DATE_FORMAT_STR_ISO8601;
}
throw new ParseException
(String.format("Cannot parse date \"%s\": while it seems to fit format '%s', parsing fails (leniency? %s)",
dateStr, formatStr, _lenient),
// [databind#1742]: Might be able to give actual location, some day, but for now
// we can't give anything more indicative
0);
}
}
3 解决办法第一种:在指定字段上加@JSonFormat注解并设置时区
@JsonFormat(timezone = "GMT+8")
private Date date;
第二种:若需要加注解的字段太多,给每个字段加注解也不是一种好方法。这时,可以在配置文件application.properties中配置时区,如下:
spring.jackson.time-zone=GMT+8
4 扩展 最后,推荐两篇不错的博文:
1)SpringMVC如何正确接收时间
2)Jackson序列化
3)彻底弄透Java处理GMT-UTC日期时间
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧