It's terrible to be integrated into OpenFeign by SpringBoot!! (recommended Collection)

Hello, I'm glacier~~

Recently, I'm using SpringBoot+K8S to develop a microservice system. Since I use K8S, I don't want to use SpringCloud. Why? Because K8S itself provides very 6 technologies for service registration and discovery, current limiting, fusing, load balancing and other micro services, why should I access spring cloud? Well, after all this, when I really use the SpringBoot+K8S technology stack, I will also encounter some problems. For example, when I don't need to use SpringCloud and call other services, I use the native OpenFegin. When I use OpenFegin to call other services, I encounter a big pit. An exception occurred in the return value LocalDateTime of the OpenFeign request. Today, let's talk about this pit!

All right, let's start~~

Project integration OpenFegin

Integrating OpenFegin dependencies

First of all, let me tell you about the project configuration. The SpringBoot version used in the overall project is 2.2.6, and the native OpenFegin uses 11.0. We use POM. Com in the following way OpenFegin is introduced into XML.

<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 omit some other configuration items.

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

Implement remote call

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

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

Next, we write a general factory class for obtaining OpenFeign client. This class is also relatively simple. In essence, it caches all feginclients with a HashMap. This FeginClient is essentially our custom Fegin interface. The Key in the cache is the basic URL for requesting connection, and the cached Value is the FeginClient interface we define.

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

Next, we will define a 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));
	}
}

Everything is ready to run the test. Ma egg, something's wrong. The main problem is that an exception will occur in the LocalDateTime field of the return value of the OpenFeign request!!!

Note: in case of 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 the problem

Problem description

SpringBoot calls the HTTP interface through the native OpenFeign client. If the return value contains the LocalDateTime type (including the time class of java.time package in other JSR-310), there may be an error of deserialization failure 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 passing a URL parameter, which is equivalent to a JSON conversion. The database takes out the data of '2020-10-07T11:04:32'. At this time, it is a time type. After entering JSON, it becomes a String type, and T becomes a character. It is no longer a special character. Therefore, the deserialization of the String "2020-10-07T11:04:32" will fail.

Problem solving

Add dependencies to the 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 in the LocalDateTime type field of the POJO class.

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

The effect after adding 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, call the remote interface again to solve the problem.

Write at the end

If you want to enter a big factory, want to be promoted and raised, or are confused about your existing work, you can communicate with me privately. I hope some of my experience can help you~~

Recommended reading:

Well, that's all for today. Let's praise, collect and comment. Let's walk up three times with one button. I'm glacier. I'll see you next time~~

Keywords: Java Spring Boot Programmer Microservices OpenFeign

Added by gaugeboson on Sat, 22 Jan 2022 18:44:15 +0200