The Pit of SpringBoot Integrating OpenFeign

  • 2021-10-27 07:22:21
  • OfStack

Directory Project Integration OpenFegin
Integrating OpenFegin dependencies
Implement remote call
Solve a problem
Problem description
Problem analysis
Problem solving

Recently, I am using SpringBoot+K8S to develop micro-service system. Since I use K8S, I don't want to use SpringCloud. Why, because K8S itself provides very 6 technologies needed for micro-services such as service registration and discovery, current limiting, fusing, load balancing, etc., why should I access SpringCloud? Well, having said that, when I really use SpringBoot+K8S, I will also encounter some problems. For example, when I don't need to use SpringCloud, when I call other services, I use the native OpenFegin, and when I use OpenFegin to call other services, I encounter a big hole. The return value LocalDateTime through OpenFeign request is abnormal. Today, let's talk about this pit!

Project Integration OpenFegin

Integrating OpenFegin dependencies

First of all, let me tell you the configuration of the project. The SpringBoot version used by the overall project is 2.2. 6, and the native OpenFegin uses 11.0. We introduce OpenFegin into pom. xml in the following ways.


<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>

Here, I omitted 1 other configuration items.

Next, I started calling remote services using OpenFegin in my project. The specific steps are as follows.

Implement remote call

First, create the OpenFeignConfig class and configure the default Contract used by OpenFegin.


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

Next, we write a general factory class for obtaining OpenFeign clients, which is also relatively simple. In essence, we cache all FeginClient with an HashMap. This FeginClient is essentially our custom Fegin interface, the cached Key is the basic URL for requesting connection, and the cached Value is our defined FeginClient interface.


public class FeginClientFactory {
 
 /**
  *  Cache all of the Fegin Client 
  */
 private volatile static Map<String, Object> feginClientCache = new HashMap<>();
 
 /**
  *  From Map Get data from 
  * @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);
 }
}

Next, we define an FeginClient interface.


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

Next, we create a test class for 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));
 }
}

1 Cut is ready to run the test. Ma Dan, there's something wrong. The main problem is that the return value LocalDateTime field through OpenFeign request will be an exception! ! !

Note: When this is an exception, the annotation we added on the LocalDateTime field is as follows.


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;

Solve a problem

Problem description

SpringBoot calls the HTTP interface through the native OpenFeign client, and if the return value contains the LocalDateTime type (including the time class of other java. time packets in JSR-310), an error of deserialization failure may occur on the client. The error message is as follows:

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')

Problem analysis

Calling fegin from the client is also equivalent to URL parameter transmission, which is equivalent to one JSON conversion. The database takes out the data of '2020-10-07T 11: 04:32', which is a time type. After entering JSON, it becomes String type, and T becomes a character. It is no longer a special character, so the string "2020-10-07T 11: 04:32" of String will fail

Problem solving

Add dependencies to your project.


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

Note: If SpringBoot is used and the SpringBoot version is explicitly specified, the version number may not be specified when jackson-datatype-jsr310 is introduced.

Next, add the following annotation to the LocalDateTime type field of the POJO class.


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

The added effect is as follows.


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;

At this point, the remote interface is called again, and the problem is solved.


Related articles: