dubbo缺省協議父類和子類擁有相同屬性導致的反序列化爲null問題排查

       今天在工作中遇到一個問題:由於自己的疏忽,定義dubbo接口入參時,WareQueryPageDTO繼承了一個父類BizCodeBaseDTO,但是有個屬性String bizCode,父類和子類中都存在。本地單元測試,接口都能根據bizCode值查詢出對應的數據,但是dubbo Consumer端查出的數據一直是錯誤的。

       檢查日誌發現,接口消費端明明傳入了bizCode值,但是卻沒有解析出來:

採用的是dubbo缺省協議,序列化方式是Hessian 二進制序列化,在反序列化時會出現父類的同名屬性覆蓋子類的同名屬性問題,所以就變成null,解析不到了。我們使用的是dubbo 2.8.4版本,這裏序列化和反序列化涉及到兩個類,在dubbo-serialization-api 包中:

序列化類:com.alibaba.com.caucho.hessian.io.JavaSerializer

反序列化類:com.alibaba.com.caucho.hessian.io.JavaDeserializer

先看一下JavaSerializer序列化方式:

 public JavaSerializer(Class cl, ClassLoader loader) {
        this.introspectWriteReplace(cl, loader);
        if (this._writeReplace != null) {
            this._writeReplace.setAccessible(true);
        }

        List primitiveFields = new ArrayList();

        ArrayList compoundFields;
        int i;
        for(compoundFields = new ArrayList(); cl != null; cl = cl.getSuperclass()) {
            Field[] fields = cl.getDeclaredFields();

            for(i = 0; i < fields.length; ++i) {
                Field field = fields[i];
                if (!Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())) {
                    field.setAccessible(true);
                    if (!field.getType().isPrimitive() && (!field.getType().getName().startsWith("java.lang.") || field.getType().equals(Object.class))) {
                        compoundFields.add(field);
                    } else {
                        primitiveFields.add(field);
                    }
                }
            }
        }

        List fields = new ArrayList();
        fields.addAll(primitiveFields);
        fields.addAll(compoundFields);
        Collections.reverse(fields);
        this._fields = new Field[fields.size()];
        fields.toArray(this._fields);
        this._fieldSerializers = new JavaSerializer.FieldSerializer[this._fields.length];

        for(i = 0; i < this._fields.length; ++i) {
            this._fieldSerializers[i] = getFieldSerializer(this._fields[i].getType());
        }

    }

在循環中先存儲子類的屬性(基本屬性primitiveFields + 引用屬性compoundFields),後存儲父類的屬性,但是

Collections.reverse(fields);

這行代碼在一些低版本中是不存在的,通過逆序操作,其實父類的屬性在前面,子類屬性在後面了。我本地debug結果也是這樣:

本例中:是WaresQueryPageDTO extends BizCodeBaseDTO,相同的屬性是String bizCode。

再看一下JavaDeserializer反序列化方式:

//組裝HashMap
 protected HashMap getFieldMap(Class cl) {
        HashMap fieldMap;
        for(fieldMap = new HashMap(); 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()) && fieldMap.get(field.getName()) == null) {
                    try {
                        field.setAccessible(true);
                    } catch (Throwable var8) {
                        var8.printStackTrace();
                    }

                    Class type = field.getType();
                    Object deser;
                    if (String.class.equals(type)) {
                        deser = new JavaDeserializer.StringFieldDeserializer(field);
                    } else if (Byte.TYPE.equals(type)) {
                        deser = new JavaDeserializer.ByteFieldDeserializer(field);
                    } else if (Short.TYPE.equals(type)) {
                        deser = new JavaDeserializer.ShortFieldDeserializer(field);
                    } else if (Integer.TYPE.equals(type)) {
                        deser = new JavaDeserializer.IntFieldDeserializer(field);
                    } else if (Long.TYPE.equals(type)) {
                        deser = new JavaDeserializer.LongFieldDeserializer(field);
                    } else if (Float.TYPE.equals(type)) {
                        deser = new JavaDeserializer.FloatFieldDeserializer(field);
                    } else if (Double.TYPE.equals(type)) {
                        deser = new JavaDeserializer.DoubleFieldDeserializer(field);
                    } else if (Boolean.TYPE.equals(type)) {
                        deser = new JavaDeserializer.BooleanFieldDeserializer(field);
                    } else if (Date.class.equals(type)) {
                        deser = new JavaDeserializer.SqlDateFieldDeserializer(field);
                    } else if (Timestamp.class.equals(type)) {
                        deser = new JavaDeserializer.SqlTimestampFieldDeserializer(field);
                    } else if (Time.class.equals(type)) {
                        deser = new JavaDeserializer.SqlTimeFieldDeserializer(field);
                    } else if (Map.class.equals(type) && field.getGenericType() != field.getType()) {
                        deser = new JavaDeserializer.ObjectMapFieldDeserializer(field);
                    } else if (List.class.equals(type) && field.getGenericType() != field.getType()) {
                        deser = new JavaDeserializer.ObjectListFieldDeserializer(field);
                    } else {
                        deser = new JavaDeserializer.ObjectFieldDeserializer(field);
                    }

                    fieldMap.put(field.getName(), deser);
                }
            }
        }

        return fieldMap;
    }

//反序列化
 public 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);
        }
    }

是把序列化中的屬性List放到HashMap中,HashMap的key用的是字符串類型:field.getName()。如果是低版本的Dubbo框架,沒有上面Collections.reverse(fields);這行代碼,的確會導致解析失敗,因爲HashMap的key是唯一的,如果沒有這行代碼,屬性List是子類的在前,父類的在後,這樣反序列化時,如例中的bizCode,子類的屬性有值,HashMap賦值了,但是後面父類中的bizCode沒有值,所以又被賦爲null了。

       到這裏,我爲什麼會遇到父類子類bizCode反序列化爲null的情況呢?我懷疑是對方Consumer端dubbo服務版本較低,是有問題的版本,可能沒有逆序父類子類順序那行代碼導致的。我本地啓動一個Provider服務和Consumer服務,發現調用是沒有問題的,數據返回正常。翌日,詢問了一下接口調用方,反饋他們使用的dubbo版本是2.6.8版本,查了一下2.6.8版本的JavaSerializer序列化方式那一段代碼:

public JavaSerializer(Class cl, ClassLoader loader) {
        this.introspectWriteReplace(cl, loader);
        if (this._writeReplace != null) {
            this._writeReplace.setAccessible(true);
        }

        ArrayList primitiveFields = new ArrayList();

        ArrayList compoundFields;
        int i;
        for(compoundFields = new ArrayList(); cl != null; cl = cl.getSuperclass()) {
            Field[] fields = cl.getDeclaredFields();

            for(i = 0; i < fields.length; ++i) {
                Field field = fields[i];
                if (!Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())) {
                    field.setAccessible(true);
                    if (!field.getType().isPrimitive() && (!field.getType().getName().startsWith("java.lang.") || field.getType().equals(Object.class))) {
                        compoundFields.add(field);
                    } else {
                        primitiveFields.add(field);
                    }
                }
            }
        }
        /**注意:沒有屬性逆轉那行代碼*/
        ArrayList fields = new ArrayList();
        fields.addAll(primitiveFields);
        fields.addAll(compoundFields);
        this._fields = new Field[fields.size()];
        fields.toArray(this._fields);
        this._fieldSerializers = new JavaSerializer.FieldSerializer[this._fields.length];

        for(i = 0; i < this._fields.length; ++i) {
            this._fieldSerializers[i] = getFieldSerializer(this._fields[i].getType());
        }

    }

可以看出,在2.6.8版本里,是沒有屬性逆轉那行代碼的,這會導致子類屬性在前,父類屬性在後,反序列化時子類的屬性值就被父類的相同屬性覆蓋了,這也解釋了爲什麼我本地啓動兩個服務調用沒問題,第三方調用有問題,因爲是第三方dubbo版本略低的問題。

      以上是個人的分析,如有錯誤,還請大家多多指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章