美文网首页
Dubbo 一次简单的序列化问题排查

Dubbo 一次简单的序列化问题排查

作者: 都是浮云啊 | 来源:发表于2019-04-28 20:32 被阅读0次

[TOC]

1. 问题

最近在做项目的时候遇到了一个问题,简单描述如下:

  1. 首先工程 A 中有一个用于网络传输的基类 BaseVO (主要封装了一些通用的返回值参数,比如我们要说的 createTime),此外它有一个子类,也就是我们的业务类 OrderDetailVO,此外还有一个接口,这个接口里有一个方法,方法的返回值就是业务类 OrderDetailVO
  2. 在工程 B 通过 DUBBO 远程调用工程 A 的这个接口获取 OrderDetailVO,发现有一个值 createTime 得到的始终是 null,看到这我第一反应其实是代码里是不是忘记设置这个值了
  3. 先通过 dubbo 的控制台命令 invoke 了一下接口,发现这个值是有返回的,于是再次到了工程 A 中跑了下单测,依然为 null。代码如下:( github 源码)

VO类

@Data
public class BaseVO implements Serializable {
    /**
     * 创建时间
     */
    private Long createTime;
}
@Data
public class OrderDetailVO extends BaseVO {
    /**
     * orderiD
     */
    private String orderId;
    /**
     * 创建时间
     */
    private Long createTime;
}



服务提供者

@Service("orderSearchService")
public class OrderSearchService implements IOrderSearchService {
   @Override
   public OrderInfoVo getOrderByOrderId(String orderId) {
       OrderInfoVo orderInfoVo = new OrderInfoVo();
       orderInfoVo.setOrderId(orderId);
       orderInfoVo.setTotalFee(1111);
       OrderDetailVO orderDetailVO = new OrderDetailVO();
       orderDetailVO.setOrderId("orderIdTest");
       orderDetailVO.setCreateTime(111111111L);
       orderInfoVo.setBaseVO(orderDetailVO);
       return orderInfoVo;
   }
}

服务消费者

@Service(value = "shopService")
public class ShopService implements IShopService {

    @Resource
    private IOrderSearchService orderSearchService;
    @Override
    public ShopOrderVo getShopOrderVo(String orderId) {
        OrderInfoVo orderInfoVo = orderSearchService.getOrderByOrderId(orderId);
        ShopOrderVo shopOrderVo = new ShopOrderVo();
        shopOrderVo.setOrderInfoVo(orderInfoVo);
        shopOrderVo.setEntityId("123123");
        return shopOrderVo;
    }
}

先是跑了个单测,发现得到的这个 createTime 值是 null


image.png

为了验证服务提供方没问题,invoke了一下服务接口,如下,发现这个值明明是已经返回了的


image.png

考虑到服务的提供方这块没问题,那就是服务的消费方有问题了,消费者这里的 createTime 显然是拿了父类 BaseVO 中的 createTime的值(父类和子类其实没有必要也不应该定义同一个值,但是在我们的项目中基类的字段太多了可能当时设计这块的人没有考虑到所以重名了),那么到这里序列化是没毛病的,极有可能是反序列化的问题。默认的 Dubbo 序列化与反序列化协议是 hessian2 协议。对着源码了解一下

2. 分析

与 Dubbo 序列化相关的类就是 com.alibaba.dubbo.common.serialize.Serialization 了,我们看下,默认使用的是 hessian2 协议进行序列化和反序列化的, (PS: SPI 无处不在~)

@SPI("hessian2")
public interface Serialization {
    byte getContentTypeId();
    String getContentType();
    /**
     * 序列化
     */
    @Adaptive
    ObjectOutput serialize(URL url, OutputStream output) throws IOException;
    /**
     * 反序列化
     */
    @Adaptive
    ObjectInput deserialize(URL url, InputStream input) throws IOException;
}

Hessian2Serialization 中序列化和反序列化方法如下,都是返回了一个 ObjectOutput 对象.当然,一个是用来序列化的,一个是用来反序列化的

public ObjectOutput serialize(URL url, OutputStream out) throws IOException {
        return new Hessian2ObjectOutput(out);
    }

    public ObjectInput deserialize(URL url, InputStream is) throws IOException {
        return new Hessian2ObjectInput(is);
    }
2.1 序列化

Hessian2ObjectOutput 中针对各种包装类型定义了对应的 writeXX 实现,我们一般用的最多的还是 writeObject() 这个方法,因为更多的我们传输的是对象,它就 1 行代码,调用了 Hessian2Output#writeObject 方法,构造了一个 Serializer,然后调用它的 JavaSerializer.writeObject() 方法

public void writeObject(Object object) throws IOException {
        if (object == null) {
            this.writeNull();
        } else {
            Serializer serializer = this.findSerializerFactory().getSerializer(object.getClass());
            serializer.writeObject(object, this);
        }
    }

JavaSerializer.writeObject() 方法主要调用 JavaSerializer.writeObject10() 这个方法循环处理需要序列化的 field 的名和值(因为我们还有个属性是 transient 这种不需要被序列化的)。

  private void writeObject10(Object obj, AbstractHessianOutput out) throws IOException {
        for(int i = 0; i < this._fields.length; ++i) {
            Field field = this._fields[i];
            out.writeString(field.getName());
            this._fieldSerializers[i].serialize(out, obj, field);
        }

        out.writeMapEnd();
    }
2.2 反序列化

Hessian2ObjectInput 中针对各种包装也定义了对应的 readXX 实现,直接来看 readObject() 方法,它调用 Hessian2Input#readObject(java.lang.Class),首先也是构造了一个 Deserializer ,它的构造方法如下:获取 fieldMap 的时候,这里塞值的时候会先去取子类的 field,然后再去取父类的 field,如果子类和父类的属性名称相同就会跳过循环,也就是说最终 map 中只有子类的值,这一步是正常的,意味着这不是获取属性去反序列化的时候的问题。

public JavaDeserializer(Class cl) {
        this._type = cl;
        //  fieldMap,这里塞值的时候会先去取子类的 field,然后再去取父类的 field,如果子类和父类的属性名称相同就会跳过循环,也就是说最终 map 中只有子类的值
        this._fieldMap = this.getFieldMap(cl);
        // readResolve
        this._readResolve = this.getReadResolve(cl);
        if (this._readResolve != null) {
            this._readResolve.setAccessible(true);
        }
        // 获取所有构造器
        Constructor[] constructors = cl.getDeclaredConstructors();
        long bestCost = 9223372036854775807L;
        // 遍历构造器,找到最佳构造器
        for(int i = 0; i < constructors.length; ++i) {
            Class[] param = constructors[i].getParameterTypes();
            long cost = 0L;

            for(int j = 0; j < param.length; ++j) {
                cost = 4L * cost;
                if (Object.class.equals(param[j])) {
                    ++cost;
                } else if (String.class.equals(param[j])) {
                    cost += 2L;
                } else if (Integer.TYPE.equals(param[j])) {
                    cost += 3L;
                } else if (Long.TYPE.equals(param[j])) {
                    cost += 4L;
                } else if (param[j].isPrimitive()) {
                    cost += 5L;
                } else {
                    cost += 6L;
                }
            }

            if (cost < 0L || cost > 65536L) {
                cost = 65536L;
            }
            cost += (long)param.length << 48;
            if (cost < bestCost) {
                this._constructor = constructors[i];
                bestCost = cost;
            }
        }
        if (this._constructor != null) {
            this._constructor.setAccessible(true);
            Class[] params = this._constructor.getParameterTypes();
            this._constructorArgs = new Object[params.length];

            for(int i = 0; i < params.length; ++i) {
                this._constructorArgs[i] = getParamArg(params[i]);
            }
        }
    }

继续找原因,当子类反序列化的时候,上面的readObject()需要调 JavaDeserializer#readMap() 方法,值的覆盖就在这里发生的,先子类后父类,子类给属性设置好值,父类还会设置自己存在的值,当 2 个值一样就会覆盖掉。如下

ublic Object readMap(AbstractHessianInput in, Object obj) throws IOException {
        try {
            int ref = in.addRef(obj);
            Object resolve;
            // 循环读取,先子后父
            while(!in.isEnd()) {
                resolve = in.readObject();
                JavaDeserializer.FieldDeserializer deser = (JavaDeserializer.FieldDeserializer)this._fieldMap.get(resolve);
                if (deser != null) {
                    // 问题所在,子类设置完值之后,父类还会设一遍,把子类的值覆盖掉
                    deser.deserialize(in, obj);
                } else {
                    in.readObject();
                }
            }
            in.readMapEnd();
            resolve = this.resolve(obj);
            if (obj != resolve) {
                in.setRef(ref, resolve);
            }
            return resolve;
        } catch (IOException var6) {
            throw var6;
        } catch (Exception var7) {
            throw new IOExceptionWrapper(var7);
        }
    }

deser.deserialize(in, obj); 反序列化的时候,会调用 FieldDeserializer.deserialize 方法 ,然后到 ObjectFieldDeserializer.deserialize 方法,这个方法就是给属性设置值的。

abstract static class FieldDeserializer {
        FieldDeserializer() {
        }
        abstract void deserialize(AbstractHessianInput var1, Object var2) throws IOException;
    } 
static class ObjectFieldDeserializer extends JavaDeserializer.FieldDeserializer {
        private final Field _field;

        ObjectFieldDeserializer(Field field) {
            this._field = field;
        }
        void deserialize(AbstractHessianInput in, Object obj) throws IOException {
            Object value = null;
            try {
                // 读取当前值
                value = in.readObject(this._field.getType());
                // 给当前属性设置值
                this._field.set(obj, value);
            } catch (Exception var5) {
                JavaDeserializer.logDeserializeError(this._field, obj, value, var5);
            }
        }
    }

总结

出现这个问题归根结底还是编码过程中的失误,无论是 Dubbo 的源码还是自身的业务代码,属性值重复定义的话,也没有这种必要,最简单的解决方法是不要让父类和子类定义同一个类型且同一个名称的字段。[基于 Dubbo 版本 2.6.3]

相关文章

网友评论

      本文标题:Dubbo 一次简单的序列化问题排查

      本文链接:https://www.haomeiwen.com/subject/cvtsnqtx.html