使用SpringBoot項目導入openfeign版本的問題

SpringBoot項目導入openfeign版本

記SpringBoot項目導入spring-cloud-starter-openfeign依賴版本問題

前幾天在寫項目時,需要微服務間的調用,於是決定使用openfeign。

由於我們的項目使用的是Spring-Boot,版本是2.1.6。

在初次導入openfeign時使用的版本時2.2.2,但是將依賴導入後,發現原先的項目啟動報錯

 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'bootstrapImportSelectorConfiguration': Initialization of bean failed; nested exception is java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationPropertiesBean
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:845) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:742) [spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
 at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:389) [spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:311) [spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
 at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:139) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
 at org.springframework.cloud.bootstrap.BootstrapApplicationListener.bootstrapServiceContext(BootstrapApplicationListener.java:212) ~[spring-cloud-context-2.2.2.RELEASE.jar:2.2.2.RELEASE]
 at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:117) ~[spring-cloud-context-2.2.2.RELEASE.jar:2.2.2.RELEASE]
 at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:74) ~[spring-cloud-context-2.2.2.RELEASE.jar:2.2.2.RELEASE]
 at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:75) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
 at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:53) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
 at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:340) [spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:304) [spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1213) [spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1202) [spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
 at com.wjs.plan.PlanApplication.main(PlanApplication.java:16) [classes/:na]
Caused by: java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationPropertiesBean
 at org.springframework.cloud.context.properties.ConfigurationPropertiesBeans.postProcessBeforeInitialization(ConfigurationPropertiesBeans.java:94) ~[spring-cloud-context-2.2.2.RELEASE.jar:2.2.2.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:414) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1770) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
 … 26 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.context.properties.ConfigurationPropertiesBean
 at java.net.URLClassLoader.findClass(URLClassLoader.java:382) ~[na:1.8.0_201]
 at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_201]
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) ~[na:1.8.0_201]
 at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_201]
 … 30 common frames omitted

找瞭一大推資料最後將openfeign版本改成2.1.1發現項目可以正常啟動瞭。

最後查看瞭openfengn兩個版本的不同,再次貼出來。

版本2.1.1

版本2.2.2

相信在這裡大佬已經看出來瞭,openfeign2.2.2版本中指定瞭每個jar包的版本,2.1.1版本則是引入瞭父工程的版本號。

在引入openfeign2.2.2後可以看到spring-cloud-context有2個,這就導致jar包沖突從而項目啟動失敗。

被SpringBoot整合OpenFeign坑慘瞭

最近,在使用SpringBoot+K8S開發微服務系統,既然使用瞭K8S,我就不想使用SpringCloud瞭。

為啥,因為K8S本身的就提供瞭非常6的服務註冊與發現、限流、熔斷、負載均衡等等微服務需要使用的技術,那我為啥還要接入SpringCloud呢?額,說瞭這麼多,在真正使用SpringBoot+K8S這一套技術棧的時候,也會遇到一些問題,比如我不需要使用SpringCloud時,調用其他服務時,我使用的是原生的OpenFegin,在使用OpenFegin調用其他服務的時候,就遇到瞭一個大坑。

通過OpenFeign請求返回值LocalDateTime發生瞭異常,今天,我們就來說說這個坑!

好瞭,我們開始吧~~

項目集成OpenFegin

集成OpenFegin依賴

首先,我先跟大傢說下項目的配置,整體項目使用的SpringBoot版本為2.2.6,原生的OpenFegin使用的是11.0,我們通過如下方式在pom.xml中引入OpenFegin。

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <skip_maven_deploy>false</skip_maven_deploy>
    <java.version>1.8</java.version>
    <openfegin.version>11.0</openfegin.version>
</properties>
<dependencies>
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-core</artifactId>
        <version>${openfegin.version}</version>
    </dependency>
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-jackson</artifactId>
        <version>${openfegin.version}</version>
    </dependency>
</dependencies>

這裡,我省略瞭一些其他的配置項。

接下來,我就開始在我的項目中使用OpenFegin調用遠程服務瞭。具體步驟如下。

實現遠程調用

首先,創建OpenFeignConfig類,配置OpenFegin默認使用的Contract。

@Configuration
public class OpenFeignConfig {
    @Bean
    public Contract useFeignAnnotations() {
        return new Contract.Default();
    }
}

接下來,我們寫一個通用的獲取OpenFeign客戶端的工廠類,這個類也比較簡單,本質上就是以一個HashMap來緩存所有的FeginClient,這個的FeginClient本質上就是我們自定義的Fegin接口,緩存中的Key為請求連接的基礎URL,緩存的Value就是我們定義的FeginClient接口。

public class FeginClientFactory { 
    /**
     * 緩存所有的Fegin客戶端
     */
    private volatile static Map<String, Object> feginClientCache = new HashMap<>();
    
    /**
     * 從Map中獲取數據
     * @return 
     */
    @SuppressWarnings("unchecked")
    public static <T> T getFeginClient(Class<T> clazz, String baseUrl){
        if(!feginClientCache.containsKey(baseUrl)) {
            synchronized (FeginClientFactory.class) {
                if(!feginClientCache.containsKey(baseUrl)) {
                    T feginClient = Feign.builder().decoder(new JacksonDecoder()).encoder(new JacksonEncoder()).target(clazz, baseUrl);
                    feginClientCache.put(baseUrl, feginClient);
                }
            }
        }
        return (T)feginClientCache.get(baseUrl);
    }
}

接下來,我們就定義一個FeginClient接口。

public interface FeginClientProxy {
    @Headers("Content-Type:application/json;charset=UTF-8")
    @RequestLine("POST /user/login")
    UserLoginVo login(UserLoginVo loginVo);
}

接下來,我們創建SpringBoot的測試類。

@RunWith(SpringRunner.class)
@SpringBootTest
public class IcpsWeightStarterTest {
    @Test
    public void testUserLogin() {
        ResponseMessage result = FeginClientFactory.getFeginClient(FeginClientProxy.class, "http://127.0.0.1").login(new UserLoginVo("zhangsan", "123456", 1));
        System.out.println(JsonUtils.bean2Json(result));
    }
}

一切準備就緒,運行測試。麻蛋,出問題瞭。主要的問題就是通過OpenFeign請求返回值LocalDateTime字段會發生異常!!!

註:此時異常時,我們在LocalDateTime字段上添加的註解如下所示。

import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
@TableField(value = "CREATE_TIME", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "zh", timezone = "GMT+8")
private LocalDateTime createTime;

解決問題

問題描述

SpringBoot通過原生OpenFeign客戶端調用HTTP接口,如果返回值中包含LocalDateTime類型(包括其他JSR-310中java.time包的時間類),在客戶端可能會出現反序列化失敗的錯誤。錯誤信息如下:

 Caused by:com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `java.time.LocalDateTime` (no Creators, like default construct, exist): no String-argument constructor/factory method to deserialize from String value ('2020-10-07T11:04:32')

問題分析

從客戶端調用fegin,也是相當於URL傳參就相當於經過一次JSON轉換,數據庫取出‘2020-10-07T11:04:32’數據這時是時間類型,進過JSON之後就變成瞭String類型,T就變成瞭字符不再是一個特殊字符,因此String的字符串“2020-10-07T11:04:32”反序列化就會失敗。

問題解決

在項目中增加依賴。

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.9.9</version>
</dependency>

註:如果是用的是SpringBoot,並且明確指定瞭SpringBoot版本,引入jackson-datatype-jsr310時,可以不用指定版本號。

接下來,在POJO類的LocalDateTime類型字段增加如下註解。

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;

添加後的效果如下所示。

import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
@TableField(value = "CREATE_TIME", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "zh", timezone = "GMT+8")
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createTime;

此時,再次調用遠程接口,問題解決。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: