问题现象
调用某个查询接口时报错:
{
"status": 500,
"error": "Internal Server Error",
"message": "java.util.LinkedHashMap cannot be cast to com.xxx.entity.QueryDto",
"path": "/api/query/list.do"
}
请求参数:
{
"name": "",
"code": "",
"type": "5",
"pageIndex": 1,
"pageSize": 20,
"param": {
"name": "",
"code": "",
"type": "5"
}
}
问题分析
1. 代码调用链路
Controller 层:
@RequestMapping(value = "list.do", method = RequestMethod.POST)
public ListResult<ResponseVO> queryList(@RequestBody QueryRequestDTO request) {
return businessService.queryList(request);
}
Service 层:
public ListResult<ResponseVO> queryList(ParamPage<QueryDto> request) {
QueryDto param = request.getParam(); // 💥 这里抛出异常
// ...
}
2. 类型继承关系
问题代码:
// ❌ 继承泛型类但未指定类型参数
public class QueryRequestDTO extends ParamPage implements Serializable {
private String name;
private String code;
private String type;
// ...
}
基类定义:
public class ParamPage<T> extends ParamBase<T> {
int pageIndex = -1;
int pageSize = -1;
}
public class ParamBase<T> implements Serializable {
T param; // 泛型字段
}
3. 问题根源
关键点:QueryRequestDTO extends ParamPage 时没有指定泛型参数。
执行流程:
-
JSON 反序列化阶段:
- Jackson 接收到 JSON,需要反序列化为
QueryRequestDTO QueryRequestDTO继承了ParamPage<T>,但没有指定T的类型- 由于泛型擦除,运行时
T的类型信息丢失 - Jackson 无法确定
param字段的具体类型 - Jackson 默认将嵌套的 JSON 对象反序列化为
LinkedHashMap
- Jackson 接收到 JSON,需要反序列化为
-
方法调用阶段:
- Controller 调用
businessService.queryList(request) QueryRequestDTO是ParamPage的子类,符合多态,编译通过- 实际传入的对象中,
param字段是LinkedHashMap
- Controller 调用
-
类型转换阶段:
- Service 方法签名:
queryList(ParamPage<QueryDto> request) - 代码执行:
QueryDto param = request.getParam() - 实际类型:
getParam()返回的是LinkedHashMap - 期望类型:
QueryDto - 抛出 ClassCastException
- Service 方法签名:
问题结论
这是一个泛型类型参数缺失导致的 JSON 反序列化异常。
- 直接原因:
QueryRequestDTO继承ParamPage时未指定泛型参数 - 技术原因:Java 泛型擦除导致 Jackson 无法推断
param字段类型,默认使用LinkedHashMap - 触发条件:Service 方法尝试将
LinkedHashMap强转为QueryDto
解决方案
方案一:指定泛型参数(推荐)
// ✅ 正确:明确指定泛型类型
public class QueryRequestDTO extends ParamPage<QueryDto> implements Serializable {
private String name;
private String code;
private String type;
// ...
}
优点:
- 类型安全,编译期就能发现问题
- Jackson 可以正确反序列化
param字段 - 无需修改其他代码
方案二:不使用 param 字段
既然 QueryRequestDTO 已经有了自己的业务字段,就不需要再使用继承来的 param 字段:
// 修改 Service 方法
public ListResult<ResponseVO> queryList(QueryRequestDTO dto) {
// 直接使用 dto 的字段
String name = dto.getName();
String code = dto.getCode();
// ...
}
优点:
- 避免泛型问题
- 代码更清晰,不需要通过
getParam()获取数据
核心教训
-
继承泛型类必须指定类型参数
extends ParamPage❌
extends ParamPage<QueryDto>✅ -
泛型擦除会导致 JSON 反序列化失败
运行时泛型类型信息丢失,Jackson 无法推断字段类型,默认使用LinkedHashMap -
编译通过不代表运行正确
由于多态,QueryRequestDTO可以赋值给ParamPage<QueryDto>,编译器不会报错,但运行时泛型参数不匹配会导致ClassCastException -
设计要合理
如果 DTO 已经有了业务字段,就不应该再使用继承来的param字段,这种设计容易产生混淆
总结
一句话:QueryRequestDTO 继承泛型类 ParamPage<T> 时未指定类型参数,导致 Jackson 将 param 字段反序列化为 LinkedHashMap,而代码期望的是 QueryDto,最终抛出类型转换异常。
解决方法:继承时明确指定泛型参数 extends ParamPage<QueryDto>。