Feign在进行序列化时遇到泛型类型的擦除,导致反序列化时成了LinkedHashMap
- 故障背景
- 问题分析
- 修复方案
- 修复方案一 避免使用泛型
- 修复方案二 解析data泛型的时候处理
故障背景
假设我们有一个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泛型的时候处理思路如下:
- 先用json工具转字符串
- 再反序列化成对象
参考代码如下所示:
@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;
}
}