1)问题描述
服务A ServiceA 远程调用服务B ServiceB(HttpClient/RestTemplet/Fegin等方式)
调用参数简单描述如下
//组装参数,大致如下,忽略处理细节
Customer c1 = new Customer();
Map map1 = new HashMap();
map1.put('user1',c1);
Customer c2 = new Customer();
Map map2 = new HashMap();
map2.put('user2',c2);
Map map3 = new HashMap();
map3.putAll(map2);
List paramList = new ArrayList();
paramList.add(map1);
paramList.add(map2);
paramList.add(map3);
//发起调用
ServiceA.invokeServiceB(paramList);
2)问题现象
调用至 ServiceB,接收的参数中 paramList 第三个元素为null,莫名缺失。
3)原因分析
i)Java远程调用(HttpClient/RestTemplet/Feign等等)无论通过何种方式调用,最终都是需要序列化后,以字节的形式传输。
i)Java对象序列化时,若包含重复的对象(相同引用),则只会序列化一次,其他的仅使用句柄。
i)Map.putAll()方法是浅拷贝。
结合以上三点,问题根源解释如下:
入参 map3是通过map.putAll()赋值,因此map3\map2 实际都是同一个对象 Customer c2,
paramList 序列化时,只会处理 map1,map2。map3不会做序列化,仅使用map2的句柄。
所以,ServiceB 处理请求,对参数反序列化时,由于map3为句柄,且ServiceB中未定义Customer对象,从而无法反序列化,导致参数缺失。
4)解决方案
远程调用时,若同一个对象可能出现多次,则需通过深拷贝创建新的对象,再进行调用。本例修改示意如下:
//组装参数,大致如下,忽略处理细节
Customer c1 = new Customer();
Map map1 = new HashMap();
map1.put('user1',c1);
Customer c2 = new Customer();
Map map2 = new HashMap();
map2.put('user2',c2);
//利用JSONObject实现深拷贝
Map map3 = JSONObeject.parseObject( JSONObject.toJSONString(map2),Map.class );
List paramList = new ArrayList();
paramList.add(map1);
paramList.add(map2);
paramList.add(map3);
//发起调用
ServiceA.invokeServiceB(paramList);
5)问题延申
问题1. 假若入参paramList中Map存放的不是自定义对象,而是int、String等类型,如下方式能正常反序列化吗?
//组装参数,大致如下,忽略处理细节
Map map1 = new HashMap();
map1.put('age',20);
map1.put('addr',"北京");
Map map2 = new HashMap();
map2.put('age',30);
map2.put('addr',"上海");
List paramList = new ArrayList();
paramList.add(map1);
paramList.add(map2);
paramList.add(map2);
//发起调用
ServiceA.invokeServiceB(paramList);
问题2. 假若在ServiceA,ServiceB中各自定义了Customer对象,且属性完全一致。(使用浅拷贝传参)最终能正常反序列化吗?
问题3. 假若自定义对象Customer 定义在公共组件common中,ServiceA,ServiceB均引用了common组件。(使用浅拷贝传参)最终能正常反序列化吗?