最近遇到一个问题,B 服务调用 A 服务时,返回值反序列化时,POJO对象变成了Map类型。在A服务单独测试的时候一直还原不了,在 B 服务进行测试的时候,跟到反序列化数据时才看到原因。
原因很简单
A 服务的接口方法返回的结果是一个Object
(或 Map<String, Object>
中的 value
),Object
的具体实现不在 A 服务的 API 包中,因此在 B 服务找不到该返回值真正的实现类,在 B 服务调用接口返回结果反序列化找不到具体的类型时,就会以 Map
类型进行实例化。
下面是简单接口示例:
在 A-api
模块中:
public interface A {
/* 注意是 Object(或 `Map<String, Object>` 中的 `value`) */
Object say();
}
在 A-service
实现中:
public class AModel implements Serializable {
private static final long serialVersionUID = 1L;
private String text;
/* 省略 getter setter */
}
public class AImpl implements A {
public Object say() {
return new AModel("hello");
}
}
在 B-service
中用到了 A-api
,但是不依赖 A-service
:
public class BImpl implements B {
@Autowire
private A a;
public void saySomething() {
Object s = a.say();
//强制转换会出错,实际的返回值类型为HashMap
AModel am = (AModel)s;
//可以下面这样调用,真正使用时这样肯定也算错误
String text = ((HashMap)am).getText();
}
}
下面是简单的分析过程。
反序列化过程:
从 Hessian2Input
开始,反序列 Object 时调用 readObjectInstance
:
上图代码中,会从 SerializerFactory.getObjectDeserializer
获取类型对应的反序列化实现。
通过一层层调用,最后到下面的方法:
在该方法中,有如下代码:
上面会反射查找类,找到就能正确的反序列化为真正的对象,如果找不到,继续看后面的代码。
可以看到这里有警告日志,这里需要特别的设置才能输出日志。
首先,这里的 log 是 java.util.Logger
实现,目前流行的日志框架一般都选择 slf4j
作为日志的中间层,这时可以通过下面依赖从 jul 桥接到 slf4j:<dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> <version>1.7.28</version> </dependency>
相关文档:Bridging legacy APIs
有了上面的依赖后,就能按照
slf4j
的方式进行输出。
回到外层代码,如下:
找不到类型对应的序列化实例时,就会使用 MapDeserializer
进行反序列化。 MapDeserializer
中的方法如下:
至此就完全清楚 Dubbo 中反序列化的一条重要原则了。