在Dubbo Provider运行时生成pojo的class
问题简述:
没有在provider中引入consumer会传过来的子类,导致provider找不到子类后会反序列化为父类实例,同时子类里面扩展的属性都会丢失,导致mybatis里面的动态sql不能访问子类属性。
具体场景:
consumer-A,调用consumer-B,consumer-B调用provider-B,其中consumer-B即充当consumer角色、也充当provider角色,consumer-A和consumer-B都引用了consumer-B-api,调用时传递的也是consumer-B-api里面的类,所以当consumer-B调用provider-B时需要将consumer-B-api里面类的实例手动转换为provider-B-api里面类的实例,嫌麻烦,所以就没转。consumer-B直接透传了consumer-A发来的参数给provider-B。
如果在provider-B中能自己生成consumer-B-api里面的类并且供mybatis访问,代码就变得简单了。
代码场景
接口及抽象父类定义如下
|
|
provider中某个具体的业务类定义如下
|
|
Consumer中单元测试类如下
|
|
暴露的问题
执行上述单元测试后,可以看到provider会有如下警告提示,并且mybatis中也访问不到子类中的属性。
|
|
同时可以看到dubbo使用的是Hessian来反序列化参数的,当找不到CatQuery后,就会使用父类BaseQuery来反序列化,这样子类所扩展的属性就丢失了。
分析代码
将断点打在打印警告的地方,查看调用链如下:
|
|
Hessian2SerializerFactory(SerializerFactory).getDeserializer(String)部分代码如下:
|
|
分析Hessian2SerializerFactory的引入
查看/META-INF/dubbo/internal/com.alibaba.dubbo.common.serialize.Serialization文件里面引入了Hessian2Serialization,Hessian2Serialization引用了Hessian2InputEnhance,同时Hessian2InputEnhance调用了Hessian2SerializerFactory。
CodecSupport使用com.alibaba.dubbo.common.serialize.Serialization文件的代码如下:
|
|
从这个代码可以看出dubbo不允许覆盖自带的Serialization,如果能否覆盖Hessian2Serialization,那么就能引入自己的代码了。
大致思路
debug时看到Hessian2Input.readObjectInstance方法传入了父类class和带有子类类名及其属性的参数Hessian2Input$ObjectDefinition,那么根据这些信息就可以在provider中构造出子类class,并且加载到当前线程Classloader中就可以了。
解决步骤
引入自己的代码
上面分析了dubbo不允许覆盖自带的Serialization,查看其加载Serialization的代码是在CodecSupport的静态块中,如果在该静态块加载完成后使用自己的Hessian2SerializationEnhance覆盖ID_SERIALIZATION_MAP中Hessian2SerializerFactory,那么目的就达到了。
随之问题就变成“在何处读取CodecSupport的ID_SERIALIZATION_MAP属性”,想到利用dubbo提供的filter扩展,在filter的构造器中就可以完成这个功能。
|
|
运行时生成pojo的class
顺着dubbo自己的思路,Hessian2SerializationEnhance引入Hessian2ObjectInputEnhance,Hessian2ObjectInputEnhance引入Hessian2SerializerFactoryEnhance。
其中Hessian2ObjectInput中readObjectInstance方法用private修饰,那就直接复制Hessian2ObjectInput源码到Hessian2ObjectInputEnhance中,在readObjectInstance方法中生成子类。代码如下:
|
|
使用javassist构造class并且使用ClassLoader加载的代码如下:
|
|