Springcloud feign传日期类型参数报错的解决方案

 更新时间:2022年03月14日 15:40:41   作者:weixin_33725126  
这篇文章主要介绍了Springcloud feign传日期类型参数报错的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

feign传日期类型参数报错

Date类型参数报错

在Spring cloud feign接口中传递Date类型参数时报错,报错信息。

场景:

客户端传递一个new Date()的参数,服务端接受的参数和客户端有时间差。

客户端打印格式化的new Date():

2018-05-11 10:23:36

而服务端接收到的参数是:

2018-05-12 00:23:36

我们从Feign启动的源码可以看出,Feign在encode和decode时会用SpringEncoder类来实现:

    @Bean
    @ConditionalOnMissingBean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
    }
 
    @Bean
    @ConditionalOnMissingBean
    public Encoder feignEncoder() {
        return new SpringEncoder(this.messageConverters);
    }

而SpringEncoder的HttpMessageConverters使用的是Jackson默认模板,该模板来自基类WebMvcConfigurationSupport.java:

    protected final List<HttpMessageConverter<?>> getMessageConverters() {
        if (this.messageConverters == null) {
            this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
            configureMessageConverters(this.messageConverters);
            if (this.messageConverters.isEmpty()) {
                addDefaultHttpMessageConverters(this.messageConverters);
            }
            extendMessageConverters(this.messageConverters);
        }
        return this.messageConverters;
    }

而WebMvcConfigurationSupport.java最终使用的是默认的ObjectMapper生成的MappingJackson2HttpMessageConverter。

至此可以看出该问题的产生并不是Feign的问题,而是Feign实现中使用的Spring MVC中的Jackson转换参数问题,默认的TimeZone并不是东八区,而是UTC。

    /**
     * Override the default {@link TimeZone} to use for formatting.
     * Default value used is UTC (NOT local timezone).
     * @since 4.1.5
     */
    public Jackson2ObjectMapperBuilder timeZone(TimeZone timeZone) {
        this.timeZone = timeZone;
        return this;
    }

这个问题,在Spring MVC中可以在接口或者字段上添加注解来解决,但在Feign中使用GET请求的接口添加注解是不行的。debug发现,Spring MVC在处理Date的时候,调用了sun.reflect.ConstructorAccessor#newInstance(Object[] var1),时间会加14个小时。具体实现没看到源码,后续再研究。需要说明的是,加JsonFormat注解对于Feign接口没生效,但Spring MVC是可以的。

OK,回到正题。要解决这个问题,最好的办法是自定义ObjectMapper。即使是加了注解可以解决问题,也依然推荐使用自定义ObjectMapper,因为大量的接口每个都添加注解太繁琐了。

    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        return Jackson2ObjectMapperBuilder.json()
                .serializationInclusion(JsonInclude.Include.NON_NULL)
                .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                .timeZone(TimeZone.getTimeZone("Asia/Shanghai"))
                .build();
    }

这样注解进去的ObjectMapper就带了时区。

LocalDate类型报错

报错详情:

Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Can not construct instance of java.time.LocalDate: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.time.LocalDate: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: java.io.PushbackInputStream@3ce2b1e2; line: 1, column: 44] (through reference chain: com.chunrun.user.param.UserParams["localDate"])

这是因为LocalDate没有提供默认的构造器,而Jackson还不支持Java8的特征。这时候只需要加上依赖,ObjectMapper加一行代码即可:

    <dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-jsr310</artifactId>
      <version>2.4.0</version>
    </dependency>
    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        return Jackson2ObjectMapperBuilder.json()
                .serializationInclusion(JsonInclude.Include.NON_NULL)
                .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                .timeZone(TimeZone.getTimeZone("Asia/Shanghai"))
                .modules(new JSR310Module())
                .build();
    }

以上配置调用方也需要。

feign传参问题及传输Date类型参数时差的坑

feign的调用如下:

List<LeftSeatCountOfDaysResp> getLeftSeatCountOfDays(
            @RequestParam("configType") Integer configType,
            @RequestParam("courseId") Long courseId,
            @RequestParam("startDateFrom") Date startDateFrom,
            @RequestParam("startDateTo") Date startDateTo,
            @RequestParam("level") Integer level); 

我们采用了两个date类型的参数传参,结果:

我们传入的时间为:

但服务端接受到的时间为:

天啊撸,竟然出现了我们并不熟悉的14h时差,并不是我们熟悉的8个小时。feign真是天坑啊。这是SpringCloud Feign传Date类型参数的时差导致的。

备注:使用date类型传参,如果是body里面用对象传,是不会出现时差问题的。

下面说说两种解决方案

  • 当发送时间类型时,直接用String发送(推荐)
  • Feign客户端实现FeignFormatterRegistrar接口自定义DateFormatRegister
@Component
    public class DateFormatRegister implements FeignFormatterRegistrar{
        public DateFormatRegister(){
        }
        @Override
        public void registerFormatters(FormatterRegistry registry) {
        registry.addConverter(Date.class, String.class, new Date2StringConverter()); 
        }
        private class Date2StringConverter implements Converter<Date,String>{
            @Override
            public String convert(Date source) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return sdf.format(source);
            }
        }
    } 

服务端实现:

@Configuration
    public class WebConfigBeans {
        @Autowired
        private RequestMappingHandlerAdapter handlerAdapter;
        /**
        * 增加字符串转日期的功能
        */
        @PostConstruct
        public void initEditableValidation() {
            ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) handlerAdapter
                        .getWebBindingInitializer();
            if (initializer.getConversionService() != null) {
                GenericConversionService genericConversionService = (GenericConversionService) initializer
                            .getConversionService();
                genericConversionService.addConverter(String.class, Date.class, new String2DateConverter());
            }
        }
    } 

第二种比较麻烦,但是一劳永逸,代码的优雅性比第一种好。但个人而言,还是推荐使用第一种。

feign传参时候使用@DateTimeFormat注解的坑

@NotNull
    @MyFuture
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date appointDate; //预定的预成班日期 

比如这个字段,服务端上面用了@DateTimeFormat注解,这样的话,springMVC手机支持直接传字符串2018-03-03自动转换的。但是,但是,如果你是用client调用,那就不报错啦,报错啦。所以使用的时候,一定要注意啊,一定要注意啊。

小结:虽然fiegn有很多坑,但咱不能说feign不好用。毕竟他比restTemplate或者httpClient还是优雅很多的,能够简化很多东西,负载均衡也做得不错,毕竟在本地就可以做。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java实现打字游戏

    Java实现打字游戏

    这篇文章主要为大家详细介绍了Java实现打字游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-08-08
  • SpringBoot3使用Swagger3的示例详解

    SpringBoot3使用Swagger3的示例详解

    本文介绍了如何在Spring Boot 3项目中使用Swagger3进行后端接口的前端展示,首先,通过添加依赖并配置application.yml文件来快速启动Swagger,然后,详细介绍了Swagger3的新注解与Swagger2的区别,并提供了一些常用注解的使用示例,感兴趣的朋友跟随小编一起看看吧
    2024-11-11
  • Java中File文件操作类的基础用法

    Java中File文件操作类的基础用法

    这篇文章主要给大家介绍了关于Java中File文件操作类基础用法的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用File类具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-07-07
  • springboot集成mybatisplus的方法

    springboot集成mybatisplus的方法

    这篇文章主要为大家详细介绍了springboot集成mybatisplus的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-04-04
  • idea项目打开后出现橙色的时钟图标的解决

    idea项目打开后出现橙色的时钟图标的解决

    本文主要介绍了idea项目打开后出现橙色的时钟图标的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • SpringMVC跨服务器上传文件中出现405错误的解决

    SpringMVC跨服务器上传文件中出现405错误的解决

    这篇文章主要介绍了SpringMVC跨服务器上传文件中出现405错误的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • IDEA运行Tomcat中文乱码出现的各种问题

    IDEA运行Tomcat中文乱码出现的各种问题

    这篇文章主要介绍了IDEA运行Tomcat中文乱码的各种问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • Java实现简单控制台版ATM系统

    Java实现简单控制台版ATM系统

    这篇文章主要为大家详细介绍了Java实现简单控制台版ATM系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • Java多线程开发工具之CompletableFuture的应用详解

    Java多线程开发工具之CompletableFuture的应用详解

    做Java编程,难免会遇到多线程的开发,但是JDK8这个CompletableFuture类很多开发者目前还没听说过,但是这个类实在是太好用了,本文就来聊聊它的应用吧
    2023-03-03
  • Java编程实现向文本文件中读取数据之Scanner用法示例

    Java编程实现向文本文件中读取数据之Scanner用法示例

    这篇文章主要介绍了Java编程实现向文本文件中读取数据之Scanner用法,结合实例形式分析了java使用Scanner类读取文本文件相关操作技巧与注意事项,需要的朋友可以参考下
    2018-03-03

最新评论