[转]dubbo序列化对象的一个坑

本文探讨了在使用Dubbo框架时遇到的一个关于反序列化的具体问题,即子类覆盖父类同名属性导致的数据丢失现象,并通过实验验证及源码分析揭示了问题的根本原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近工作中遇见了一个小问题,在此记录一下,大致是这样的,有一父类,有一个属性traceId,主要是记录日志号,这样可以把所有日志串起来,利于排查问题,所有的pojo对象继承于此,但是其中一同事在子类pojo中也增加了这一个属性,在消费者端给traceId设置了值,但经过序列化解析后,提供者端这个traceId时,值为空,解决问题很简单啊,把子类中的traceId属性去掉搞定。

    虽然问题很好解决但是这让笔者很懵逼啊,什么状况,都清楚地,实例化的子类,私有属性,取的肯定是实例设定的值,虽然我对此深信不疑,但是这还是让我怀疑了我自己,于是写了如下一些代码的验证这个问题。

1.先把问题抛出来一下。

Consumer端代码

@Setter
@Getter
@ToString
public class BaseBean implements Serializable {
    private String xxx;
    private String yyy;
    private Integer zzz;
}
@Setter
@Getter
@ToString
public class Bean extends BaseBean {
    private String xxx;
    private String yyy;
    private Integer zzz;
    private String myStr;
}
public class Consumer {

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                new String[] { "applicationContext.xml" });
        context.start();

        DemoService demoService = (DemoService) context.getBean("demoService");
        Bean bean = new Bean();
        bean.setMyStr("123");
        bean.setXxx("xxx");
        bean.setYyy("yyy");
        bean.setZzz(789);
        String hello = demoService.serTest(bean);
        System.out.println(hello);
        System.in.read();
}

Provider端代码

public class DemoServiceImpl implements DemoService {
    public String serTest(Bean bean) {
        System.out.println(bean);
        return "123";
    }
}

运行结果:

这里写图片描述

2.Java中序列化就真的会出现这样的问题?

代码如下

public class TestSeriali {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Bean bean = new Bean();
        bean.setMyStr("123");
        bean.setXxx("xxx");
        bean.setYyy("yyy");
        bean.setZzz(789);
        ObjectOutputStream out  = new ObjectOutputStream(new FileOutputStream("chongming"));
        out.writeObject(bean);
        System.out.println("序列化完毕..");
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("chongming"));
        Bean beanResult = (Bean) in.readObject();
        System.out.println("反序列化完毕..");
        System.out.println(beanResult);
    }
}

这段代码很显然父类三个属性,子类四个属性,其中三个与父类相同。代码运行结果如下

这里写图片描述

注:dubbo支持的其余集中序列化方式也做了验证,结果都是一样的,在这里就略过了。

这段代码证实了笔者一直的想法还是对的,但是问题就是出在dubbo的反序列化了。好吧翻翻dubbo的反序列化的源码吧,看看到底是咋回事

3.具体原因研究

代码比较多,挑几点重要的记录下,首先反序列化的类是JavaSerializer。

这个类的构造方法里调用了这样的方法getFieldMap,把里面本类和父类的所有方法放到一个fieldMap里,因为是HashMap,为了保证方法名不覆盖,这个方法里做了一个操作就是fieldMap.get(field.getName()) != null,有的话就继续循环下去不覆盖,这样的话如果有同名的方法,那只有子类的方法在里面。还有这个类Hessian2Input要说下,其中的方法readObjectInstance,它会取到本类和父类的所有方法放到一个数组fieldNames下,这些说完了说到这里面反序列化的方法JavaSerializer的readObject,是按fieldNames数组循环取值,在流里面挨个取出来,一直赋给本类的set方法,先是有值的,到父类时,取到的为空,就把本类的值覆盖了。到这里原因就清楚了。

主要的代码贴下来好了,如下

JavaSerializer构造方法及getFieldMap方法,获取到fieldMap

public JavaDeserializer(Class cl)
  {
    _type = cl;
    _fieldMap = getFieldMap(cl);
.......
protected HashMap getFieldMap(Class cl)
  {
    HashMap fieldMap = new HashMap();

    for (; cl != null; cl = cl.getSuperclass()) {
      Field []fields = cl.getDeclaredFields();
      for (int i = 0; i < fields.length; i++) {
        Field field = fields[i];

        if (Modifier.isTransient(field.getModifiers())
        || Modifier.isStatic(field.getModifiers()))
          continue;
        else if (fieldMap.get(field.getName()) != null)
          continue;
......

Hessian2Input的readObjectInstance

private Object readObjectInstance(Class cl, ObjectDefinition def)
    throws IOException
  {
    String type = def.getType();
    String []fieldNames = def.getFieldNames();
......

JavaSerializer的readObject,这个贴的全一点

public Object readObject(AbstractHessianInput in,
               Object obj,
               String []fieldNames)
    throws IOException
  {
    try {
      int ref = in.addRef(obj);

      for (int i = 0; i < fieldNames.length; i++) {
        String name = fieldNames[i];
                //重名的话,取出的都是私有的属性
        FieldDeserializer deser = (FieldDeserializer) _fieldMap.get(name);

        if (deser != null)      // 当in读到父类时,把本类的属性值覆盖掉了
      deser.deserialize(in, obj);
        else
          in.readObject();
      }

      Object resolve = resolve(obj);

      if (obj != resolve)
    in.setRef(ref, resolve);

      return resolve;
    } catch (IOException e) {
      throw e;
    } catch (Exception e) {
      throw new IOExceptionWrapper(obj.getClass().getName() + ":" + e, e);
    }
  }

其中注释俩句是笔者加的,在此原因也找到了,问题很好解决,这部分源码也不是很难,这些全是自己翻源码看的,可能我的理解也不完全对,如果不对谁看见了,欢迎交流。


作者: 重名 

出处: http://www.cnblogs.com/daily-note

此文为转载,如有侵权请联系删除

### Dubbo 序列化源码解析 Dubbo序列化机制位于 `org.apache.dubbo.common.serialize` 包下。主要类包括: - **Serialization**: 接口定义了序列化和反序列化的标准操作。 - **ObjectOutput/ObjectInput**: 定义对象写入和读取的具体实现。 对于 FastJSON 和 Hessian2 这两种不同的序列化方式,对应的实现类分别为: #### FastJson 实现 FastJSON 是默认的序列化器,在 3.2 版本中被设置为默认选项[^1]。其核心逻辑由 `com.alibaba.fastjson.JSON` 类处理。 ```java public class FastjsonSerialization extends AbstractSerialization { @Override protected ObjectOutput createOutputStream(OutputStream out) throws IOException { return new FastjsonObjectOutput(out); } @Override protected ObjectInput createInputStream(InputStream in) throws IOException { return new FastjsonObjectInput(in); } } ``` #### Hessian2 实现 Hessian2 提供了一种二进制协议来优化网络传输效率。当配置文件指定了 `prefer-serialization: hessian2` 后,将会启用该序列化模式。 ```java public class Hessian2Serialization extends AbstractSerialization { @Override public byte getContentTypeId() { return CONTENT_TYPE_HESSIAN2; } @Override public String getContentType() { return MIME_APPLICATION_HESSIAN2; } @Override protected ObjectOutput createOutputStream(OutputStream out) throws IOException { return new Hessian2ObjectOutput(out); } @Override protected ObjectInput createInputStream(InputStream is) throws IOException { return new Hessian2ObjectInput(is); } } ``` 在遇到未注册异常的情况下,切换到 Hessian2 可以有效解决问题,因为 Hessian2 对于自定义类型的兼容性更好。 为了确保安全性并允许特定类进行反序列化,可以在配置文件中指定白名单路径 `security/serialize.allowlist` 或者调整安全策略模式[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值