map insert异常失败_处理dubbo反序列化失败的坑
前言
??今天下午,當(dāng)我經(jīng)過一個(gè)小時(shí)的奮”鍵“疾”碼“,準(zhǔn)備好好的審查一下(摸魚)自己寫的代碼,經(jīng)過一段時(shí)間審查(摸的差不多了,該下班了),得出一個(gè)結(jié)論我寫的代碼很優(yōu)雅、精簡(jiǎn)。所以大手一揮提交代碼,并在 API 管理系統(tǒng)上將 xxx 接口點(diǎn)了個(gè)完成。準(zhǔn)備收拾東西走人了準(zhǔn)點(diǎn)下班。然而事與愿違,沒過多久前端大哥就@我了,說 xxx 接口有問題,麻煩處理一下。內(nèi)心第一反應(yīng)(你丫的參數(shù)傳錯(cuò)了吧)卑微的我只能默默的回個(gè),好的、麻煩把參數(shù)給我一下,我這邊檢查一下[微笑臉]。
場(chǎng)景還原
??經(jīng)過測(cè)試,發(fā)現(xiàn)確實(shí)是我的問題。還好沒甩鍋,要不然就要被打臉了。錯(cuò)誤信息如下:
{??"code":?"010000",
??"message":"java.util.HashMap?cannot?be?cast?to?com.aixiao.inv.common.dto.tax.AddEmployeeDTO$Employee",
??"data":?null
}
??看到這個(gè)錯(cuò)誤有點(diǎn)懵,HashMap 無法轉(zhuǎn)換為AddEmployeeDTO$Employee。內(nèi)心在想,沒道理啊。請(qǐng)求參數(shù)我都是拷貝過來的,壓根就沒用Map進(jìn)行參數(shù)傳遞。畢竟我都是個(gè)老手了,咋可能犯這樣愚蠢的錯(cuò)誤。俗話說遇到問題不要慌,讓我們掏出手機(jī)先發(fā)個(gè)朋友圈,不對(duì)好像有點(diǎn)跑題了,我們先看一下調(diào)用鏈的數(shù)據(jù)傳遞。
??首先 web 將AddEmployeeForm數(shù)據(jù)傳遞到服務(wù)端,然后使用fromToDTO()方法,進(jìn)行將數(shù)據(jù)轉(zhuǎn)換為 Dubbo 請(qǐng)求需要的AddEmployeeDTO。Dubbo 服務(wù)放接收AddEmployeeDTO后,使用 EmployeeConvert 將數(shù)據(jù)轉(zhuǎn)換為AddEmployeeXmlReq再執(zhí)行相關(guān)邏輯。
AddEmployeeForm 類
@Datapublic?class?AddEmployeeForm?implements?Serializable?{
????/**
?????*?職員信息列表
?????*/
????private?List?employees;@Datapublic?static?class?Employee?implements?Serializable?{/**
?????????*?姓名
?????????*/private?String?name;/**
?????????*?工作
?????????*/private?String?job;
????}
}
FormToDTO()方法
public??T?formToDTO(F?form,?T?dto)?{//?進(jìn)行數(shù)據(jù)拷貝????BeanUtils.copyProperties(form,?dto);//?返回?cái)?shù)據(jù)return?dto;
}
AddEmployeeDTO 類
@Datapublic?class?AddEmployeeDTO?implements?Serializable?{
????/**
?????*?職員信息列表
?????*/
????private?List?employees;@Datapublic?static?class?Employee?implements?Serializable?{/**
?????????*?姓名
?????????*/private?String?name;/**
?????????*?工作
?????????*/private?String?job;
????}
}
EmployeeConvert 轉(zhuǎn)換類
?EmployeeConvert 轉(zhuǎn)換類,使用了mapstruct進(jìn)行實(shí)現(xiàn),沒使用過的小伙伴可以簡(jiǎn)單的了解下。
?@Mapperpublic?interface?EmployeeConvert?{
????EmployeeConvert?INSTANCE?=?Mappers.getMapper(EmployeeConvert.class);
????AddEmployeeXmlReq?dtoToXmlReq(AddEmployeeDTO?dto);
}
AddEmployeeXmlReq 類
@Datapublic?class?AddEmployeeXmlReq?implements?Serializable?{
????/**
?????*?職員信息列表
?????*/
????private?List?employees;@Datapublic?static?class?Employee?implements?Serializable?{/**
?????????*?姓名
?????????*/private?String?name;/**
?????????*?工作
?????????*/private?String?job;
????}
}
EmployeeController
@RestController@AllArgsConstructor
public?class?EmployeeController?{
????private?final?EmployeeRpcProvider?provider;
????@PostMapping("/employee/add")
????public?ResultVO?employeeAdd(@RequestBody?AddEmployeeForm?form)?{
????????provider.add(formToDTO(form,new?AddEmployeeDTO()));
????????return?ResultUtil.success();
????}
}
EmployeeRpcServiceImpl
@Slf4j@Service
public?class?EmployeeRpcServiceImpl?implements?EmployeeService?{
????@Override
????public?ResultDTO?add(AddEmployeeDTO?dto)?{
????????log.info("dubbo-provider-AddEmployeeDTO:{}",?JSON.toJSONString(dto));
????????AddEmployeeXmlReq?addEmployeeXmlReq?=?EmployeeConvert.INSTANCE.dtoToXmlReq(dto);
????????return?ResultUtil.success();
????}
}
分析原因
判斷異常拋出點(diǎn)
??我們需要先確定異常是在consumer 拋出的還是provider拋出的。判斷過程很簡(jiǎn)單,我們可以進(jìn)行本地debug,看看是執(zhí)行到哪里失敗了就知道了。如果不方便本地調(diào)試,我們可以在關(guān)鍵點(diǎn)上打上相應(yīng)的日志。比如說consumer調(diào)用前后,provider處理前后。如果請(qǐng)求正常 日志打印的順序應(yīng)該是:
這樣通過觀察日志就可以判定異常是在哪里拋出的了。
?實(shí)際并沒有這樣麻煩,因?yàn)樵?consumer 做了 rpc 異常攔截,所以我當(dāng)時(shí)看了下 consumer 的日志就知道是 provider 拋出來的。
?找到出錯(cuò)的代碼
??既然找到了出問題是出在provider,那看是什么原因?qū)е碌?#xff0c;從前面的調(diào)用鏈可以知道,provider接收到AddEmployeeDTO會(huì)使用EmployeeConvert將其轉(zhuǎn)換為AddEmployeeXmlReq,所以我們可以打印出AddEmployeeDTO看看consumer的傳參是否正常。
??通過日志我們可以發(fā)現(xiàn)consumer將參數(shù)正常的傳遞過來了。那么問題應(yīng)該就出在EmployeeConvert將AddEmployeeDTO轉(zhuǎn)換為AddEmployeeXmlReq這里了。由于EmployeeConvert是使用mapstruct進(jìn)行實(shí)現(xiàn),我們可以看看自動(dòng)生成的轉(zhuǎn)換類實(shí)現(xiàn)邏輯是咋樣的。
??通過觀察源代碼可以發(fā)現(xiàn),在進(jìn)行轉(zhuǎn)換的時(shí)候需要傳入一個(gè)List 而這個(gè)Employee正是AddEmployeeDTO.Employee。這個(gè)時(shí)候可能會(huì)困擾了,我明明就是傳入AddEmployeeDTO,而且類里面壓根就沒有Map,為啥會(huì)拋出java.util.HashMap cannot be cast to com.aixiao.inv.common.dto.tax.AddEmployeeDTO$Employee這個(gè)異常呢?
讓我們Debug一下看看發(fā)生了啥。
??這個(gè)時(shí)候你會(huì)發(fā)現(xiàn)接收到的AddEmployeeDTO.employees內(nèi)存儲(chǔ)的并不是一個(gè)AddEmployeeDTO$Employee對(duì)象,而是一個(gè)HashMap。那看來真相大白了,原來是 dubbo 反序列化的時(shí)候?qū)ddEmployeeDTO$Employee 轉(zhuǎn)換為HashMap了。從而導(dǎo)致了java.util.HashMap cannot be cast to com.aixiao.inv.common.dto.tax.AddEmployeeDTO$Employee異常的拋出。
你以為結(jié)束了?
??為啥Dubbo反序列化時(shí)會(huì)將AddEmployeeDTO$Employee變成Map呢?我們回過頭看看之前打印參數(shù)的日志,有一個(gè)警告日志提示了java.lang.ClassNotFoundException:com.aixiao.inv.api.model.form.AddEmployeeForm$Employee ,找不到AddEmployeeForm$Employee這個(gè)就有點(diǎn)奇怪了,為啥不是AddEmployeeDTO$Employee?
??在進(jìn)行dubbo調(diào)用前AddEmployeeForm會(huì)使用fromToDTO()方法將其轉(zhuǎn)化為AddEmployeeDTO。那么問題會(huì)不會(huì)出現(xiàn)在這里呢?我們繼續(xù)Debug看看。
??嘔吼,這下石錘了。原來是在formToDTO的時(shí)候出問題了。傳遞過去AddEmployeeDTO內(nèi)部的Employee竟然變成了AddEmployeeForm$Employee。這也是為什么provider那邊會(huì)拋出java.lang.ClassNotFoundException:com.aixiao.inv.api.model.form.AddEmployeeForm$Employee的原因了。審查一下formToDTO的代碼看看為啥會(huì)發(fā)生這樣的情況:
public??T?formToDTO(F?form,?T?dto)?{//?進(jìn)行數(shù)據(jù)拷貝????BeanUtils.copyProperties(form,?dto);//?返回?cái)?shù)據(jù)return?dto;
}
??fromToDTO內(nèi)的代碼非常精簡(jiǎn),就一個(gè)BeanUtils.copyProperties()的方法,那毫無疑問它就是罪魁禍?zhǔn)琢恕Mㄟ^在 baidu 的海洋里遨游,我找到了原因。原來是BeanUtils是淺拷貝造成的。淺拷貝只是調(diào)用子對(duì)象的 set 方法,并沒有將所有屬性拷貝。(也就是說,引用的一個(gè)內(nèi)存地址),所以在轉(zhuǎn)換的時(shí)候,將AddEmployeeDTO內(nèi)的employees屬性指向了AddEmployeeForm的employees的內(nèi)存地址。所以將在進(jìn)行調(diào)用時(shí),Dubbo因?yàn)榉葱蛄谢瘯r(shí)找不到對(duì)應(yīng)的類,就會(huì)將其轉(zhuǎn)換為Map。
小結(jié)一下
??上面的問題,主要是由于 BeanUtils 淺拷貝造成。并且引發(fā)連鎖反應(yīng),造成Dubbo反序列化異常以及EmployeeConvert的轉(zhuǎn)換異常,最后拋出了java.util.HashMap cannot be cast to com.aixiao.inv.common.dto.tax.AddEmployeeDTO$Employee 錯(cuò)誤信息。
解決方法
??既然知道了問題出現(xiàn)的原因,那么解決起來就很簡(jiǎn)單了。對(duì)于單一的屬性,那么不涉及到深拷貝的問題,適合用 BeanUtils 繼續(xù)進(jìn)行拷貝。但是涉及到集合我們可以這樣處理:
簡(jiǎn)單粗暴使用 foreach 進(jìn)行拷貝。
使用 labmda 實(shí)現(xiàn)進(jìn)行轉(zhuǎn)換。
dto.setEmployees(form.getEmployees().stream().map(tmp?->?{
??AddEmployeeDTO.Employee?employee?=?new?AddEmployeeDTO.Employee();
??BeanUtils.copyProperties(tmp,employee);
??return?employee;
}).collect(Collectors.toList()));
dto.setEmployees(convertList(form.getEmployees(),AddEmployeeDTO.Employee.class));
public??List?convertList(List?source,?Class?targetClass)?{return?JSON.parseArray(JSON.toJSONString(source),?targetClass);
}
總結(jié)
參考
- BeanUtils.copyProperties 的使用(深拷貝,淺拷貝)
結(jié)尾
??我是不一樣的科技宅,每天進(jìn)步一點(diǎn)點(diǎn),體驗(yàn)不一樣的生活。我們下期見!
??如果覺得對(duì)你有幫助,可以多多評(píng)論,多多點(diǎn)贊哦,也可以到我的主頁看看,說不定有你喜歡的文章,也可以隨手點(diǎn)個(gè)關(guān)注哦,謝謝。
總結(jié)
以上是生活随笔為你收集整理的map insert异常失败_处理dubbo反序列化失败的坑的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hive udf 分组取top1_Hiv
- 下一篇: python 整数输出 d f_pyth