Feign在进行序列化时遇到泛型类型的擦除,导致反序列化时成了LinkedHashMap

故障背景

假设我们有一个Feign接口

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;

@FeignClient(name = "testJdkDateTimeRpcService", url = "${query-current-service-provider.prevBaseUrl}")
public interface TestJdkDateTimeRpcService {
    /**
     * 测试JDK Date 类型序列化和反序列化
     * @param testJdkDateTimeParam
     * @return
     */
    @PostMapping(value = "/rpc-service/testJdkDateTimeRpcService/fetchParamIncludeJdkDateTime.do",
            consumes = MediaType.ALL_VALUE,
            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    VueElementAdminResponse fetchParamIncludeJdkDateTime(@SpringQueryMap TestJdkDateTimeParam testJdkDateTimeParam);
}

其中这个接口响应结果对象中包含有泛型,比如Object,

@Data
public class VueElementAdminResponse implements Serializable {
    private static final long serialVersionUID = -3368531539063907497L;
    private Integer code;
    private String message;
    private Object data;
    private String trackId = SmartStringUtils.getSnowFlakeStrId();
}

还有一个业务对象

import lombok.Data;

import java.io.Serializable;

@Data
public class TestJdkDateTimeParam implements Serializable {
    private static final long serialVersionUID = -5346602757850243509L;
    private String id;
    private String testDate;
}

然后我们定义了一个Feign 调用接口

import org.springframework.web.bind.annotation.*;

@RequestMapping(value = "/rpc-service/testJdkDateTimeRpcService")
@RestController
public class TestJdkDateTimeRpcController {

    @PostMapping(value = "/fetchParamIncludeJdkDateTime.do")
    public VueElementAdminResponse fetchParamIncludeJdkDateTime(@ModelAttribute TestJdkDateTimeParam testJdkDateTimeParam){
        VueElementAdminResponse vueElementAdminResponse=new VueElementAdminResponse();
        vueElementAdminResponse.setCode(20000);
        vueElementAdminResponse.setMessage("/rpc-service/testJdkDateTimeRpcService/fetchParamIncludeJdkDateTime.do-测试Feign调用时JDK DateTime 序列化和反序列化");
        vueElementAdminResponse.setData(testJdkDateTimeParam);
        return vueElementAdminResponse;
    }
}

值得注意的是:
我们在VueElementAdminResponse 对象的data中赋值 一个TestJdkDateTimeParam对象

最后再定义一个本地调用的接口:

@Slf4j
@RequestMapping(value = "/test-service/testJdkDateTimeRpcService")
@RestController
public class TestJdkDateTimeWebController {

    private final TestJdkDateTimeRpcService testJdkDateTimeRpcService;

    public TestJdkDateTimeWebController(TestJdkDateTimeRpcService testJdkDateTimeRpcService) {
        this.testJdkDateTimeRpcService = testJdkDateTimeRpcService;
    }

    @PostMapping(value = "/testParamIncludeJdkDateTime.do")
    public VueElementAdminResponse fetchParamIncludeJdkDateTime(){
        TestJdkDateTimeParam testJdkDateTimeParam=new TestJdkDateTimeParam();
        testJdkDateTimeParam.setTestDate(DateUtil.formatDate(new Date()));
        testJdkDateTimeParam.setId(SmartStringUtils.getSnowFlakeStrId());
        
        VueElementAdminResponse vueElementAdminResponse=testJdkDateTimeRpcService.fetchParamIncludeJdkDateTime(testJdkDateTimeParam);
        // 错误用法 会抛出异常 java.util.LinkedHashMap cannot be cast to com.xxx.business.test.model.TestJdkDateTimeParam
        TestJdkDateTimeParam testJdkDateTimeParam1= (TestJdkDateTimeParam)vueElementAdminResponse.getData();
        log.info("test:{}",testJdkDateTimeParam1);
        return vueElementAdminResponse;
    }
}

如果我们尝试使用如下代码

TestJdkDateTimeParam testJdkDateTimeParam1= (TestJdkDateTimeParam)vueElementAdminResponse.getData();

执行会报错如下:

java.lang.ClassCastException: 

java.util.LinkedHashMap cannot be cast to com.xxx.business.test.model.TestJdkDateTimeParam

问题分析

调试我们会发现我们放入的TestJdkDateTimeParam对象变成了LinkedHashMap
在这里插入图片描述

为什么呢?

  • 在使用 Feign 进行远程调用时,如果请求或响应中包含了复杂对象(例如自定义的 POJO 类),Feign 需要将这些对象序列化成某种格式,以便在网络上进行传输。默认情况下,Feign 使用 Jackson 库来处理序列化和反序列化操作。
  • 如果您观察到 Feign 将 Object 对象的类型序列化成 LinkedHashMap,可能是由于以下原因:
    • 缺少序列化信息: 当 Feign 在进行序列化时,它需要了解对象的类型信息,以便正确地进行反序列化。如果没有正确的类型信息,Feign 可能会将对象序列化成一个类似于 LinkedHashMap 的格式,以保留一些关键信息(如字段名),但是丢失了对象的实际类型信息。
    • 泛型类型擦除: Java 的泛型在编译后会进行类型擦除,这意味着在运行时对象的具体类型信息可能会丢失。Feign 在进行序列化时可能会遇到泛型类型的擦除,导致无法准确地识别对象的实际类型。

也就是说:

在使用 Feign 进行远程调用时,如果请求或响应中包含了复杂对象(例如自定义的 POJO 类),且这个对象中包含有Object 泛型

那么 Feign 会将 Object 对象的类型序列化成 LinkedHashMap,解析时候如果不注意经常容易出现类似如下错误

java.lang.ClassCastException: 
 java.util.LinkedHashMap cannot be cast to com.xxx.business.test.model.TestJdkDateTimeParam 

修复方案

修复方案一 避免使用泛型

当使用Feign 接口调用的时候避免使用泛型,即重新定义一个Feign统一返回的VO对象:

public class VueElementAdminRpcResponseDTO implements Serializable {
    private static final long serialVersionUID = -3368531539063907497L;
    private Integer code;
    private String message;
    private String data;
    private String trackId = SmartStringUtils.getSnowFlakeStrId();
}

修复方案二 解析data泛型的时候处理

如果不想换,那么只需要解析反序列化的时候处理下也行。

解析data泛型的时候处理思路如下:

  1. 先用json工具转字符串
  2. 再反序列化成对象

参考代码如下所示:

@Slf4j
@RequestMapping(value = "/test-service/testJdkDateTimeRpcService")
@RestController
public class TestJdkDateTimeWebController {

    private final TestJdkDateTimeRpcService testJdkDateTimeRpcService;

    public TestJdkDateTimeWebController(TestJdkDateTimeRpcService testJdkDateTimeRpcService) {
        this.testJdkDateTimeRpcService = testJdkDateTimeRpcService;
    }

    @PostMapping(value = "/testParamIncludeJdkDateTime.do")
    public VueElementAdminResponse fetchParamIncludeJdkDateTime(){
        TestJdkDateTimeParam testJdkDateTimeParam=new TestJdkDateTimeParam();
        testJdkDateTimeParam.setTestDate(DateUtil.formatDate(new Date()));
        testJdkDateTimeParam.setId(SmartStringUtils.getSnowFlakeStrId());
        VueElementAdminResponse vueElementAdminResponse=testJdkDateTimeRpcService.fetchParamIncludeJdkDateTime(testJdkDateTimeParam);
        // 错误用法 会抛出异常 java.util.LinkedHashMap cannot be cast to com.xxx.business.test.model.TestJdkDateTimeParam
        // TestJdkDateTimeParam testJdkDateTimeParam1= (TestJdkDateTimeParam)vueElementAdminResponse.getData();
        // 正确用法
        // 对象序列化成字符串
        String dataJson=SmartJackSonUtils.writeObjectToJSon(vueElementAdminResponse.getData());
        // 反序列化成对象或集合
        TestJdkDateTimeParam testJdkDateTimeParam1= SmartJackSonUtils.readValueToObject(dataJson, TestJdkDateTimeParam.class);
        log.info("test:{}",testJdkDateTimeParam1);
        return vueElementAdminResponse;
    }
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐