openFeign,我的时间呢?

前言

最近公司的项目需要从单体架构变为微服务架构,于是乎对项目的代码进行了重构,在处理服务之间的调用问题时,我们使用了openFeign组件,开发的时候都很顺利,但是当传递的参数使用的是Date类型的时候,却出现了意想不到的问题。

问题重现

版本说明:

spring-boot:2.1.10.RELEASE

spring-cloud:Greenwich.SR5

spring-cloud-alibaba:2.1.2.RELEASE

Feign服务端

application.yml

server:
  port: 3001
spring:
  application:
    name: feign-service

  # nacos配置
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8800

启动类

@SpringBootApplication
@EnableDiscoveryClient // 注册服务到nacos
@Slf4j
public class FeignServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignServiceApplication.class, args);
        log.info("feign服务已启动...");
    }
}

controller

@RestController
@RequestMapping("/api")
@Slf4j
public class FeignServiceController {

    @RequestMapping(value = "service/test", method = RequestMethod.GET)
    public void feignDateTest(@RequestParam("date") Date date) {
        log.info("传递过来的时间参数:{}", date);
    }

}

Feign客户端

application.yml

server:
  port: 3002
spring:
  application:
    name: client-service

  # nacos配置
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8800

启动类

@SpringBootApplication
@EnableFeignClients // 扫描所有使用注解@FeignClient定义的feign客户端
@Slf4j
public class FeignClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignClientApplication.class, args);
    }
}

controller

@RestController
@RequestMapping("/api")
@Slf4j
public class FeignClientController {

    @Autowired
    private IFeignClient iFeignClient;

    @RequestMapping(value = "test", method = RequestMethod.GET)
    public void feignDateTest() {
        Date date = new Date();
        log.info("date参数:{}", date);
        // 调用接口
        iFeignClient.feignDateTest(date);
    }
}

启动两个服务,浏览器进行调用:http://localhost:3002/api/test

c.test.controller.FeignClientController  : date参数:Tue Apr 09 11:08:20 CST 2024
c.t.controller.FeignServiceController    : 传递过来的时间参数:Wed Apr 10 01:08:20 CST 2024

发现这两个时间居然不一样?openfeign,我的时间呢? 而且我发现这两个时间相差了14个小时

原因分析

原来,在进行网络通信时,openfeign客户端会将Date类型对象转换为String类型,会将Date类型转为String类型,比如上面的date参数,又因为中国的时区是CTS,所以转化为’Tue Apr 09 11:08:20 CST 2024’。

openfeign客户端会将Date类型对象转换为String类型,这是因为在网络传输中,只能传递文本数据而无法直接传递对象。
Date类型是Java中表示日期和时间的对象,它包含了年、月、日、时、分、秒等信息。然而,在进行网络通信时,常用的协议(如HTTP)只支持传输字符串类型的数据。
因此,为了在网络传输中传递日期和时间信息,需要将Date类型对象转换为String类型。这一过程称为日期格式化。通过使用特定的日期格式(例如ISO 8601标准格式),将Date对象转换为字符串,可以确保在不同系统、不同编程语言之间能够正确地传递和解析日期。

当服务端接收的参数有Date类型时,会调用Date的构造函数Date(String s),再调用parse(String s)方法,parse(String s)方法会根据时区的偏移量进行时间的解析,若不指定时区,默认使用本地时区。

@Deprecated
public Date(String s) {
    this(parse(s));
}

@Deprecated
public static long parse(String s) {
    
}

CTS代表的时区有四个
Central Standard Time (USA) UT-6:00 美国
Central Standard Time (Australia) UT+9:30 澳大利亚
China Standard Time UT+8:00 中国
Cuba Standard Time UT-4:00)古巴

然而,在解析时间时,JVM看到CTS则认为是美国中部的时间(UT-6:00),而系统会检测到系统的时区是中国,就会自动加14个小时(东八区与西六区的时差)。

终于真相大白,原来出问题就在这个parse(String s)方法上,但是改方法已经有了替代方法DateFormat.parse(String s).

在这里插入图片描述

解决方案

方案一:

直接使用long型的时间戳来传递时间,或者直接用String传递,到服务端去转换

方案二:

使用@DateTimeFormat格式化参数

// 客户端
@Component
@FeignClient(name = "feign-service")
public interface IFeignClient {

    @RequestMapping(value = "/api/service/test", method = RequestMethod.GET)
    void feignDateTest(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @RequestParam("date") Date date);
}

// 服务端
@RestController
@RequestMapping("/api")
@Slf4j
public class FeignServiceController {

    @RequestMapping(value = "service/test", method = RequestMethod.GET)
    public void feignDateTest(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @RequestParam("date") Date date) {
        log.info("传递过来的时间参数:{}", date);
    }

}

方案三:

自定义Feign客户端中的日期格式化,规定好将Date参数类型转化为String的格式

客户端:

import org.springframework.cloud.openfeign.FeignFormatterRegistrar;
import org.springframework.format.FormatterRegistry;
import org.springframework.stereotype.Component;
import org.springframework.core.convert.converter.Converter;

import java.util.Date;
import java.text.SimpleDateFormat;

/**
 * 自定义的日期格式化注册器,用于将Date类型转换为String类型,并指定特定的日期格式。
 */
@Component
public class DateFormatRegister implements FeignFormatterRegistrar {

    
    public DateFormatRegister() {
    }

    /**
     * 实现FeignFormatterRegistrar接口中的registerFormatters方法,用于注册自定义的日期转换器。
     *
     * @param registry FormatterRegistry对象,用于注册自定义的转换器。
     */
    @Override
    public void registerFormatters(FormatterRegistry registry) {
        // 将Date类型转换为String类型的转换器注册到FormatterRegistry中
        registry.addConverter(Date.class, String.class, new Date2StringConverter());
    }

    /**
     * 内部类,实现Converter接口,用于将Date类型转换为指定格式的String类型。
     */
    private class Date2StringConverter implements Converter<Date, String> {

        /**
         * 实现Converter接口中的convert方法,用于将Date对象转换为指定格式的字符串。
         *
         * @param source 要转换的Date对象。
         * @return 转换后的String对象,表示指定格式的日期字符串。
         */
        @Override
        public String convert(Date source) {
            // 创建SimpleDateFormat对象,指定日期格式为"yyyy-MM-dd HH:mm:ss"
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            // 使用SimpleDateFormat对象格式化Date对象,得到指定格式的日期字符串
            return sdf.format(source);
        }
    }
}

客户端定义好了,服务端呢?也需要与之对应的解析转化器

服务端:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import javax.annotation.PostConstruct;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;


/**
 * 用于配置字符串到日期的转换功能的配置类。
 */
@Configuration
public class String2DateConfig {

    // 注入RequestMappingHandlerAdapter,用于获取WebBindingInitializer
    @Autowired
    private RequestMappingHandlerAdapter handlerAdapter;

    /**
     * 在初始化阶段增加字符串转换为日期的功能。
     */
    @PostConstruct
    public void initEditableValidation() {
        // 获取WebBindingInitializer
        ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) handlerAdapter.getWebBindingInitializer();
        // 检查是否设置了ConversionService
        if (initializer.getConversionService() != null) {
            // 获取ConversionService
            GenericConversionService genericConversionService = (GenericConversionService) initializer.getConversionService();
            // 添加String到Date的转换器
            genericConversionService.addConverter(String.class, Date.class, new String2DateConverter());
        }
    }

    /**
     * 内部类,实现Converter接口,用于将String类型转换为Date类型。
     */
    private class String2DateConverter implements Converter<String, Date> {
        /**
         * 将String类型转换为Date类型。
         *
         * @param source 要转换的字符串。
         * @return 转换后的Date对象,表示转换后的日期。
         */
        @Override
        public Date convert(String source) {
            // 创建SimpleDateFormat对象,指定日期格式为"yyyy-MM-dd HH:mm:ss"
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                // 解析字符串为Date对象
                return simpleDateFormat.parse(source);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
}

再次调用,可以看出时间就一样了
在这里插入图片描述
在这里插入图片描述

至此,时间终于找回来了!

原文链接: openFeign,我的时间呢?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值