0%

修复FeighClient关于Date类型不能正确解悉的问题

最近遇到一个奇怪的问题,就是在传递Date类型的数据时,从前端到API端,是正确的,但是从API端通过FeignClient去访问Service RPC的时候,发现Date多了14小时。

在升级最新的SpringCloud之前是没有问题的,却在这个时候大感冒,让人很头痛。幸好,最终解决问题。

其实产生这个问题的原因很简单,FeignClient在传递Date类型参数时,会直接调用toString方法,在把String转回Date的时候,又是直接new Date(text)

1
2
3
4
5
6
Date date = new Date();
System.out.println(date);

String text = date.toString();
date = new Date(text);
System.out.println(date);

输出为:

1
2
Wed Nov 27 17:59:58 CST 2019
Thu Nov 28 07:59:58 CST 2019

可见明显输出的两个时间相差14小时。

主要是这里CST的问题,因为CST能代表四种不同的时间:

1
2
3
4
中部标准时区(北美洲),Central Standard Time,UT-6:00
澳洲中部时间,Central Standard Time,UT+9:30
北京时间,China Standard Time,UT+8:00
古巴标准时间,Cuba Standard Time,UT-4:00

只是这一次匹配了北美时间而已。

所以,要解决上述的问题,我们需要明确指定FeignClient在访问RPC时不再直接使用toString方式对Date类型进行转换,同时,也要更改Service RPC在输出Date类型时,也不能直接使用toString的方式输出。

我们使用DateFormat类来解决这个问题:

1
2
3
4
5
6
7
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
System.out.println(date);

String text = dateFormat.format(date);
date = dateFormat.parse(text);
System.out.println(date);

输出:

1
2
Wed Nov 27 18:06:32 CST 2019
Wed Nov 27 18:06:32 CST 2019

可见,DateFormat的确解决了这个问题,那么我们该怎么应用在FeignClient中呢?

其实网络上已经有很多文章说这个事情了,都是一堆代码,没有分析思路,只知道怎么做,但不知道为什么这么做,而且网上的代码是错的,会产生循环引用的问题。

在FeignClient访问RPC时,会牵扯两个端:

  • 服务客户端,也就是FeighClient本身。
  • 服务提供端,也就是Service RPC端。

非JSON请求 / 应答

服务客户端会将请求参数转化成字符串,然后提交请求访问服务提供端服务提供端则将请求参数转化成对应的类型,然后执行相应的服务代码。而服务提供端处理数据后返回数据则以同样的方式将结果数据转化成字符串,然后返回给服务客户端服务客户端收到后,将字符串转成类型数据,完成整个RPC调用过程。

对于这种方式,需要更改FeignClient对Date类型的转成String的机制及String转成Date的机制,对于SpringBoot来说,需要在客户端注册一个转化类:

1
2
3
4
5
6
7
8
9
10
public class DateCoverter implements Converter<Date, String> {

static final String PATTERN = "yyyy-MM-dd HH:mm:ss";

@Override
public String convert(Date date) {
DateFormat dateFormat = new SimpleDateFormat(PATTERN);
return dateFormat.format(date);
}
}

在AppConfig中进行注册:

1
2
3
4
5
6
7
8
9
@Configuration
@Slf4j
public class StarterConfiguration implements FeignFormatterRegistrar {

@Override
public void registerFormatters(FormatterRegistry registry) {
registry.addConverter(Date.class, String.class, new DateCoverter());
}
}

而在服务端,则需要注册一个Formatter,用于将String转成Date

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DateFormatter implements Formatter<Date> {

static final String PATTERN = "yyyy-MM-dd HH:mm:ss";

@Override
public Date parse(String text, Locale locale) throws ParseException {
DateFormat dateFormat = new SimpleDateFormat(PATTERN);
return dateFormat.parse(text);
}

@Override
public String print(Date object, Locale locale) {
DateFormat dateFormat = new SimpleDateFormat(PATTERN);
return dateFormat.format(object);
}
}

在AppConfig中进行注册:

1
2
3
4
5
6
7
8
9
@Configuration
@Slf4j
public class StarterConfiguration implements WebFluxConfigurer {

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new DateFormatter());
}
}

如果一个服务,即是客户端也是服务端,则可集成放在一起做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DateFormatter implements Formatter<Date>, Converter<Date, String> {

static final String PATTERN = "yyyy-MM-dd HH:mm:ss";

@Override
public Date parse(String text, Locale locale) throws ParseException {
DateFormat dateFormat = new SimpleDateFormat(PATTERN);
return dateFormat.parse(text);
}

@Override
public String print(Date object, Locale locale) {
DateFormat dateFormat = new SimpleDateFormat(PATTERN);
return dateFormat.format(object);
}

@Override
public String convert(Date date) {
DateFormat dateFormat = new SimpleDateFormat(PATTERN);
return dateFormat.format(date);
}
}

在AppConfig中进行注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@Slf4j
public class StarterConfiguration implements WebFluxConfigurer, FeignFormatterRegistrar {

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new DateFormatter());
}


@Override
public void registerFormatters(FormatterRegistry registry) {
registry.addConverter(Date.class, String.class, new DateFormatter());
}
}

JSON请求 / 应答

服务客户端通过调用Jackson库的类,将数据转成JSON字符串,而 服务提供端则将JSON串转成类型。

因为JSON是经Jackson库处理的,所以只需要配置SpringBoot的application.yaml文件中关于Jackson的配置即可:

1
2
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=Asia/Shanghai

这里就已经基本解决了上述遇到的问题,但是,SpringCloud这样的坑真的让人很恼火。