怎么解决Beanutils造成dubbo反序列化失败问题
本篇内容介绍了“怎么解决Beanutils造成dubbo反序列化失败问题”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
成都创新互联公司-专业网站定制、快速模板网站建设、高性价比海沧网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式海沧网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖海沧地区。费用合理售后完善,十余年实体公司更值得信赖。
场景还原
经过测试,发现确实是我的问题。还好没甩锅,要不然就要被打脸了。错误信息如下:
{ "code": "010000", "message":"java.util.HashMap cannot be cast to com.aixiao.inv.common.dto.tax.AddEmployeeDTO$Employee", "data": null }
看到这个错误有点懵,HashMap
无法转换为AddEmployeeDTO$Employee
。内心在想,没道理啊。请求参数我都是拷贝过来的,压根就没用Map
进行参数传递。毕竟我都是个老手了,咋可能犯这样愚蠢的错误。俗话说遇到问题不要慌,让我们掏出手机先发个朋友圈,不对好像有点跑题了,我们先看一下调用链的数据传递。
首先web将AddEmployeeForm
数据传递到服务端,然后使用fromToDTO()
方法,进行将数据转换为Dubbo请求需要的AddEmployeeDTO
。Dubbo服务放接收AddEmployeeDTO
后,使用 EmployeeConvert
将数据转换为AddEmployeeXmlReq
再执行相关逻辑。
AddEmployeeForm类
@Data public class AddEmployeeForm implements Serializable { /** * 职员信息列表 */ private Listemployees; @Data public static class Employee implements Serializable { /** * 姓名 */ private String name; /** * 工作 */ private String job; } }
FormToDTO()方法
publicT formToDTO(F form, T dto) { // 进行数据拷贝 BeanUtils.copyProperties(form, dto); // 返回数据 return dto; }
AddEmployeeDTO类
@Data public class AddEmployeeDTO implements Serializable { /** * 职员信息列表 */ private Listemployees; @Data public static class Employee implements Serializable { /** * 姓名 */ private String name; /** * 工作 */ private String job; } }
EmployeeConvert转换类
EmployeeConvert转换类,使用了mapstruct进行实现,没使用过的小伙伴可以简单的了解下。
@Mapper public interface EmployeeConvert { EmployeeConvert INSTANCE = Mappers.getMapper(EmployeeConvert.class); AddEmployeeXmlReq dtoToXmlReq(AddEmployeeDTO dto); }
AddEmployeeXmlReq类
@Data public class AddEmployeeXmlReq implements Serializable { /** * 职员信息列表 */ private Listemployees; @Data public 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(); } }
分析原因
判断异常抛出点
我们需要先确定异常是在consumer
抛出的还是provider
抛出的。判断过程很简单,我们可以进行本地debug
,看看是执行到哪里失败了就知道了。如果不方便本地调试,我们可以在关键点上打上相应的日志。比如说consumer
调用前后,provider
处理前后。如果请求正常 日志打印的顺序应该是:
这样通过观察日志就可以判定异常是在哪里抛出的了。
实际并没有这样麻烦,因为在consumer做了rpc异常拦截,所以我当时看了下consumer的日志就知道是provider抛出来的。
找到出错的代码
既然找到了出问题是出在provider
,那看是什么原因导致的,从前面的调用链可以知道,provider
接收到AddEmployeeDTO
会使用EmployeeConvert
将其转换为AddEmployeeXmlReq
,所以我们可以打印出AddEmployeeDTO
看看consumer
的传参是否正常。
通过日志我们可以发现consumer
将参数正常的传递过来了。那么问题应该就出在EmployeeConvert
将AddEmployeeDTO
转换为AddEmployeeXmlReq
这里了。由于EmployeeConvert
是使用mapstruct进行实现,我们可以看看自动生成的转换类实现逻辑是咋样的。
通过观察源代码可以发现,在进行转换的时候需要传入一个List
而这个Employee
正是AddEmployeeDTO.Employee
。这个时候可能会困扰了,我明明就是传入AddEmployeeDTO
,而且类里面压根就没有Map
,为啥会抛出java.util.HashMap cannot be cast to com.aixiao.inv.common.dto.tax.AddEmployeeDTO$Employee
这个异常呢?
让我们Debug
一下看看发生了啥。
这个时候你会发现接收到的AddEmployeeDTO.employees
内存储的并不是一个AddEmployeeDTO$Employee
对象,而是一个HashMap
。那看来真相大白了,原来是dubbo反序列化的时候将AddEmployeeDTO$Employee
转换为HashMap
了。从而导致了java.util.HashMap cannot be cast to com.aixiao.inv.common.dto.tax.AddEmployeeDTO$Employee
异常的抛出。
你以为结束了?
为啥Dubbo
反序列化时会将AddEmployeeDTO$Employee
变成Map
呢?我们回过头看看之前打印参数的日志,有一个警告日志提示了java.lang.ClassNotFoundException:com.aixiao.inv.api.model.form.AddEmployeeForm$Employee
,找不到AddEmployeeForm$Employee
这个就有点奇怪了,为啥不是AddEmployeeDTO$Employee
?
在进行dubbo
调用前AddEmployeeForm
会使用fromToDTO()
方法将其转化为AddEmployeeDTO
。那么问题会不会出现在这里呢?我们继续Debug
看看。
呕吼,这下石锤了。原来是在formToDTO
的时候出问题了。传递过去AddEmployeeDTO
内部的Employee
竟然变成了AddEmployeeForm$Employee
。这也是为什么provider
那边会抛出java.lang.ClassNotFoundException:com.aixiao.inv.api.model.form.AddEmployeeForm$Employee
的原因了。审查一下formToDTO
的代码看看为啥会发生这样的情况:
publicT formToDTO(F form, T dto) { // 进行数据拷贝 BeanUtils.copyProperties(form, dto); // 返回数据 return dto; }
fromToDTO
内的代码非常精简,就一个BeanUtils.copyProperties()
的方法,那毫无疑问它就是罪魁祸首了。通过在baidu的海洋里遨游,我找到了原因。原来是BeanUtils
是浅拷贝造成的。浅拷贝只是调用子对象的set方法,并没有将所有属性拷贝。(也就是说,引用的一个内存地址),所以在转换的时候,将AddEmployeeDTO
内的employees
属性指向了AddEmployeeForm
的employees
的内存地址。所以将在进行调用时,Dubbo
因为反序列化时找不到对应的类,就会将其转换为Map
。
小结一下
上面的问题,主要是由于BeanUtils浅拷贝造成。并且引发连锁反应,造成Dubbo
反序列化异常以及EmployeeConvert
的转换异常,最后抛出了java.util.HashMap cannot be cast to com.aixiao.inv.common.dto.tax.AddEmployeeDTO$Employee
错误信息。
解决方法
既然知道了问题出现的原因,那么解决起来就很简单了。对于单一的属性,那么不涉及到深拷贝的问题,适合用BeanUtils继续进行拷贝。但是涉及到集合我们可以这样处理:
简单粗暴使用foreach进行拷贝。
使用labmda实现进行转换。
AddEmployeeDTO dto = new AddEmployeeDTO(); dto.setEmployees(form.getEmployees().stream().map(tmp -> { AddEmployeeDTO.Employee employee = new AddEmployeeDTO.Employee(); BeanUtils.copyProperties(tmp,employee); return employee; }).collect(Collectors.toList()));
封装一个转换类进行转换。
AddEmployeeDTO dto = new AddEmployeeDTO(); dto.setEmployees(convertList(form.getEmployees(),AddEmployeeDTO.Employee.class)); publicListconvertList(List source, ClasstargetClass) { return JSON.parseArray(JSON.toJSONString(source), targetClass); }
“怎么解决Beanutils造成dubbo反序列化失败问题”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注创新互联网站,小编将为大家输出更多高质量的实用文章!
网页标题:怎么解决Beanutils造成dubbo反序列化失败问题
本文URL:http://scjbc.cn/article/gdjjes.html