Feign deserialization MismatchedInputException:Cannot deserialize instance of `Boolean` out of START_OBJECT token

overview

This article records a problem encountered in the development of microservices. For microservices, you can search for other columns of the author.

After splitting a single application into several microservices, interface requests and calls between different microservices use Feign. Generally speaking, a microservice corresponds to a database, App1 corresponds to Db1, and APP2 corresponds to Db2. If under a certain business requirement, you want to query or update two databases at the same time, that is, Db1 and Db2, you need to consider choosing one service to provide a remote interface and another service to request. As for which service to provide an interface to choose, it depends on the specific business scenario.

question

When developing locally, the debug mode starts two applications: merchant and payment. When the application starts, both are registered to the consul registration center, and the merchant service calls the interface provided by the payment service. Each service has its own unit test class. Through the unit test in the payment, it is verified in advance that a specific interface method of the payment service can pass the unit test. This method is the remote interface that the merchant service needs to call.

Then, debug the merchant service interface through postman. Unexpectedly, the postman interface request fails, and the
detailed error information printed by the console console is found as follows:

feign.codec.DecodeException: Error while extracting response for type [class java.lang.Boolean] and content type [application/json;charset=UTF-8];
nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.Boolean` out of START_OBJECT token;
nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.Boolean` out of START_OBJECT token
at [Source: (ByteArrayInputStream); line: 1, column: 1]
at com.aba.open.merchant.service.impl.MerchantAppServiceImpl.add(MerchantAppServiceImpl.java:109)

Troubleshoot

When encountering an error report, the first reaction is to look confused, the code seems to be ok.

The second reaction is to search for the error message on Google, but for a while, no relevant articles that are more suitable for the above error reporting scenario can be found.

So I can only continue to look at the code. As mentioned above, in the payment service, interface methods can pass unit tests. Symptoms of the problem: The merchant service calls the method provided by the payment service, and you can see the print log in the payment service. The logic is executed normally, a new piece of data is added to the database, and the interface responds normally. The program continues to execute, returns to the merchant service, and then reports an error.

In other words, the error occurred between service calls (nonsense). In fact, the error message is very obvious, JSON deserialization failed.

So far, still confused.

Search globally for the method provided by the payment service in the merchant project initialChannelPayGoodsList, and find that this method is also used in another business function module.
insert image description here
Was this function normal before? have no idea!

Holding a skeptical attitude is also verifying. So it is good to request the controller layer interface method corresponding to this function through postman simulation! !

Then replace the synchronous request call with an asynchronous request call, that is, replace the problematic line of code below in the above screenshot with the above problem-free form: CompletableFuture.runAsync(() -> remotePaymentService.initialChannelPayGoodsList(channelId));.

The error message disappears.

in-depth

If you [resolve] the problem at this point, you think you're done. It may never grow.

The error is [disappeared], but why?

Feign

At the same time, continue to Google search and find a similar article FeignClient call .

Considering that the problem is the remote call between applications, it is only at this time that the focus is on Feign.

So carefully check the Feign interface code, the Feign interface provided by the payment service is as follows:

@FeignClient(name = "payment-provider", fallbackFactory = RemotePaymentServiceFallbackFactory.class, configuration = FeignConfig.class)
public interface RemotePaymentService {
    
    
	/**
	 * 初始化渠道配置产品信息
	 *
	 * @param channel channel
	 */
	@RequestMapping(value = "/pay/initialChannelPayGoodsList", method = {
    
    RequestMethod.POST})
	Boolean initialChannelPayGoodsList(@RequestBody String channel);
}

The controller interface methods provided in the payment service are as follows:

@RestController
public class PayGoodsController {
    
    

	@ApiOperation(value = "初始化渠道配置产品信息", notes = "初始化查询渠道配置产品信息")
	@PostMapping(value = "/pay/initialChannelPayGoodsList")
	public Response<Boolean> initialChannelPayGoodsList(@RequestBody String channel) {
    
    
	    return Response.success(Boolean.TRUE);
	}
}

The return value types of the method definitions in the two places are inconsistent.

Changing any place to be consistent with the interface definition in another place can solve the problem.

Recommended practice: Wrap the returned data Response.

Attached Responsedefinitions (with omissions):

@Data
public class Response<T> implements Serializable {
    
    
	private static String SUCCESS = "success";
    private static String FAIL = "fail";
    private static final int SUCCESS_CODE = 0;
    private static final int ERROR_CODE = 9000;
    private int code;
    private String msg;
    private T data;
}

CompletableFuture

Then, why the interface method definitions in the previous two places are not completely consistent, that is, when the return value type is different, CompletableFuture.runAsync(() -> remotePaymentService.initialChannelPayGoodsList(channelId));there is no problem in using it?

Refer to CompletableFuture of Multithreading and Concurrency Series

If an exception occurs during the execution of the CompletableFuture method, an exception will be thrown when get and join are called to obtain the task result.

In other words, if only CompletableFuture runAsync()is used, the exceptions generated during the execution of the called method (here remotePaymentService.initialChannelPayGoodsList(channelId)) in other processes will be swallowed, and the error log will not be printed.

In fact, afterCompletableFuture.runAsync(() -> remotePaymentService.initialChannelPayGoodsList(channelId)); this line of code is executed , the data in the corresponding database in the payment service changes, and the function is completed; the merchant service does not print error logs, and the functions provided by other code fragments of the merchant service are also realized.remotePaymentService.initialChannelPayGoodsList(channelId)

Create the illusion that everything is perfect. In fact, it leaves a large or small bug hidden danger.

to reflect

The article is written here, and after reading it all the way, the thinking is very clear.

In fact, during the troubleshooting process, it was not so smooth. At first, the focus was not on Feign.

reference

Guess you like

Origin blog.csdn.net/lonelymanontheway/article/details/130560476