文档章节

Dubbo序列化的Bug及相应修复过程

林中漫步
 林中漫步
发布于 2017/03/27 12:26
字数 1960
阅读 666
收藏 0

1. 问题描述

我发现通过dubbo调用某类接口时,若该接口的返回类型是Collection的子类并且扩展了自实现的属性, 则返回结果集会丢失自实现的属性。 比如, 我有个接口方法返回PageList(源码如下),则当客户端调用这个方法时,得到的返回对象中并没有totalCount。 输入图片说明

2. 问题定位

开启debug, 跟入源码,发现dubbo是依赖hessian完成序列化与反序列化的。以下是针对Collecltion对象的序列化类:

package com.alibaba.com.caucho.hessian.io;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * Serializing a JDK 1.2 Collection.
 */
public class CollectionSerializer extends AbstractSerializer {
    private boolean _sendJavaType = true;

    /**
     * Set true if the java type of the collection should be sent.
     */
    public void setSendJavaType(boolean sendJavaType) {
        _sendJavaType = sendJavaType;
    }

    /**
     * Return true if the java type of the collection should be sent.
     */
    public boolean getSendJavaType() {
        return _sendJavaType;
    }

    public void writeObject(Object obj, AbstractHessianOutput out)
            throws IOException {
        if (out.addRef(obj))
            return;

        Collection list = (Collection) obj;

        Class cl = obj.getClass();
        boolean hasEnd;

        if (cl.equals(ArrayList.class)
                || !_sendJavaType
                || !Serializable.class.isAssignableFrom(cl))
            hasEnd = out.writeListBegin(list.size(), null);
        else
            hasEnd = out.writeListBegin(list.size(), obj.getClass().getName());

        Iterator iter = list.iterator();
        while (iter.hasNext()) {
            Object value = iter.next();

            out.writeObject(value);
        }

        if (hasEnd)
            out.writeListEnd();
    }
}

以上代码,方法 writeObject(Object obj, AbstractHessianOutput out) 的入参obj,其实相当于以上的PageList对象。仔细看就发现这个方法只对聚集的成员进行了写入,而没有对聚集外的其它属性进行写入,从而导致属性丢失。

3. 问题解决

于是, 我尝试对以上序列化类CollectionSerializer进行修改,当然还要同步修改相应的反序列化类。 以下是修改后的代码, 已通过注释标出:

package com.alibaba.com.caucho.hessian.io;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * Serializing a JDK 1.2 Collection.
 */
public class CollectionSerializer extends AbstractSerializer {
    private boolean _sendJavaType = true;

    /**
     * Set true if the java type of the collection should be sent.
     */
    public void setSendJavaType(boolean sendJavaType) {
        _sendJavaType = sendJavaType;
    }

    /**
     * Return true if the java type of the collection should be sent.
     */
    public boolean getSendJavaType() {
        return _sendJavaType;
    }

    public void writeObject(Object obj, AbstractHessianOutput out)
            throws IOException {
        if (out.addRef(obj))
            return;

        Collection list = (Collection) obj;

        Class cl = obj.getClass();
        boolean hasEnd;

        if (cl.equals(ArrayList.class)
                || !_sendJavaType
                || !Serializable.class.isAssignableFrom(cl))
            hasEnd = out.writeListBegin(list.size(), null);
        else
            hasEnd = out.writeListBegin(list.size(), obj.getClass().getName());

              /**
         * 修改序列化过程丢失属性的bug, 对继承自Collection并扩展了新属性的类,对其新增属性序列化。

         *
         * Added By HuQingmiao(443770574@qq.com) on 2017-03-25.
         */
        /** begin **/
        try {
            Class clasz = list.getClass();

            //记录已经写过的子类属性,以防被同名父类属性覆盖
            Set<String> fieldNameSet = new HashSet<String>();

            // 从当前自定义List子类逐层向上处理,对各层属性进行序列化
            for (; !clasz.getName().startsWith("java."); clasz = clasz.getSuperclass()) {

                // 如果当前类直接实现了List或Set接口,则不对其元素进行读写. 2017-08-28
                boolean impListOrSet = false;
                for (Class c : clasz.getInterfaces()) {
                    if (List.class.equals(c) | Set.class.equals(c) | SortedSet.class.equals(c) | Collection.class.equals(c)) {
                        impListOrSet = true;
                        break;
                    }
                }
                if (impListOrSet) {
                    continue;
                }

                // 如果当前类直接继承AbstractCollection/AbstractList/ABstractSet类,则不对其元素进行读写. 2017-08-29
                Class sc = clasz.getSuperclass();
                if (AbstractList.class.equals(sc) | AbstractSet.class.equals(sc) | AbstractCollection.class.equals(sc)) {
                    continue;
                }

                Field[] fields = clasz.getDeclaredFields();
                for (Field field : fields) {
                    //log.debug(">> " + clasz.getSimpleName() + "." + field.getName() + " " + field.getType());

                    // 子类属性已被写入,不再写入同名父属性
                    if (fieldNameSet.contains(field.getName())) {
                        continue;
                    }
                    if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }
                    boolean isAccessible = field.isAccessible();
                    if (!isAccessible) {
                        field.setAccessible(true);
                    }

                    Object val = field.get(list);
                    //log.debug(">> "+clasz.getSimpleName()+" "+field.getName()+" "+field.getType()+" "+val);

                    out.writeObject(val);
                    field.setAccessible(isAccessible);

                    // 记录已写过的属性
                    fieldNameSet.add(field.getName());
                }
            }// end for (; !clasz.getName()

            fieldNameSet.clear();

        } catch (IllegalAccessException e) {
            throw new IOException(e.getMessage());
        }
        /** end **/


        Iterator iter = list.iterator();
        while (iter.hasNext()) {
            Object value = iter.next();

            out.writeObject(value);
        }

        if (hasEnd)
            out.writeListEnd();
    }
}
/*
 * Copyright (c) 2001-2004 Caucho Technology, Inc.  All rights reserved.
 *
 * The Apache Software License, Version 1.1
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Caucho Technology (http://www.caucho.com/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Hessian", "Resin", and "Caucho" must not be used to
 *    endorse or promote products derived from this software without prior
 *    written permission. For written permission, please contact
 *    info@caucho.com.
 *
 * 5. Products derived from this software may not be called "Resin"
 *    nor may "Resin" appear in their names without prior written
 *    permission of Caucho Technology.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author Scott Ferguson
 */

package com.alibaba.com.caucho.hessian.io;


import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Deserializing a JDK 1.2 Collection.
 */
public class CollectionDeserializer extends AbstractListDeserializer {

    //private Logger log = LoggerFactory.getLogger(this.getClass());

    private Class _type;

    public CollectionDeserializer(Class type) {
        _type = type;
    }

    public Class getType() {
        return _type;
    }

    public Object readList(AbstractHessianInput in, int length)
            throws IOException {
        Collection list = createList();

        in.addRef(list);

        /**
         * 解决序列化过程丢失属性的bug,对继承自Collection并扩展了新属性的类,对其新增属性反序列化。
         *
         * Added By HuQingmiao(443770574@qq.com) on 2017-03-25.
         */
        /** begin **/
        try {
            Class clasz = list.getClass();

            //记录已经读过的子类属性,以防被同名父类属性覆盖
            Set<String> fieldNameSet = new HashSet<String>();

            // 从当前自定义List子类逐层向上处理,对各层属性进行反序列化
            for (; !clasz.getName().startsWith("java."); clasz = clasz.getSuperclass()) {

                // 如果当前类直接实现了List或Set接口,则不对其元素进行读写. 2017-08-28
                boolean impListOrSet = false;
                for (Class c : clasz.getInterfaces()) {
                    if (List.class.equals(c) | Set.class.equals(c) | SortedSet.class.equals(c) | Collection.class.equals(c)) {
                        impListOrSet = true;
                        break;
                    }
                }
                if (impListOrSet) {
                    continue;
                }

                // 如果当前类直接继承AbstractCollection/AbstractList/ABstractSet类,则不对其元素进行读写. 2017-08-29
                Class sc = clasz.getSuperclass();
                if (AbstractList.class.equals(sc) | AbstractSet.class.equals(sc) | AbstractCollection.class.equals(sc)) {
                    continue;
                }

                Field[] fields = clasz.getDeclaredFields();
                for (Field field : fields) {
                    //log.debug(">>2 " + clasz.getSimpleName() + "." + field.getName() + " " + field.getType());

                    // 子类属性已被读取,不再读取同名父属性
                    if (fieldNameSet.contains(field.getName())) {
                        continue;
                    }
                    if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }
                    boolean isAccessible = field.isAccessible();
                    if (!isAccessible) {
                        field.setAccessible(true);
                    }

                    Object val = in.readObject();
                    //log.debug(">>2 " + clasz.getSimpleName() + "." + field.getName() + " " + field.getType() + " " + val);

                    field.set(list, val);
                    field.setAccessible(isAccessible);

                    // 记录已记取的属性
                    fieldNameSet.add(field.getName());
                }
            }// end for (; !clasz.getName()

            fieldNameSet.clear();

        } catch (IllegalAccessException e) {
            throw new IOException(e.getMessage());
        }
        /** end **/


        while (!in.isEnd())
            list.add(in.readObject());

        in.readEnd();

        return list;
    }

    public Object readLengthList(AbstractHessianInput in, int length)
            throws IOException {
        Collection list = createList();

        in.addRef(list);

        /**
         * 解决序列化过程丢失属性的bug,对继承自Collection并扩展了新属性的类,对其新增属性反序列化。
         *
         * Added By HuQingmiao(443770574@qq.com) on 2017-03-25.
         */
        /** begin **/
        try {
            Class clasz = list.getClass();

            //记录已经读过的子类属性,以防被同名父类属性覆盖
            Set<String> fieldNameSet = new HashSet<String>();

            // 从当前自定义List子类逐层向上处理,对各层属性进行反序列化
            for (; !clasz.getName().startsWith("java."); clasz = clasz.getSuperclass()) {

                // 如果当前类直接实现了List或Set接口,则不对其元素进行读写. 2017-08-28
                boolean impListOrSet = false;
                for (Class c : clasz.getInterfaces()) {
                    if (List.class.equals(c) | Set.class.equals(c) | SortedSet.class.equals(c) | Collection.class.equals(c)) {
                        impListOrSet = true;
                        break;
                    }
                }
                if (impListOrSet) {
                    continue;
                }

                // 如果当前类直接继承AbstractCollection/AbstractList/ABstractSet类,则不对其元素进行读写. 2017-08-29
                Class sc = clasz.getSuperclass();
                if (AbstractList.class.equals(sc) | AbstractSet.class.equals(sc) | AbstractCollection.class.equals(sc)) {
                    continue;
                }

                Field[] fields = clasz.getDeclaredFields();
                for (Field field : fields) {
                    //log.debug(">>2 " + clasz.getSimpleName() + "." + field.getName() + " " + field.getType());

                    // 子类属性已被读取,不再读取同名父属性
                    if (fieldNameSet.contains(field.getName())) {
                        continue;
                    }
                    if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }
                    boolean isAccessible = field.isAccessible();
                    if (!isAccessible) {
                        field.setAccessible(true);
                    }

                    Object val = in.readObject();
                    //log.debug(">>2 " + clasz.getSimpleName() + "." + field.getName() + " " + field.getType() + " " + val);

                    field.set(list, val);
                    field.setAccessible(isAccessible);

                    // 记录已记取的属性
                    fieldNameSet.add(field.getName());
                }
            }// end for (; !clasz.getName()

            fieldNameSet.clear();

        } catch (IllegalAccessException e) {
            throw new IOException(e.getMessage());
        }
        /** end **/

        for (; length > 0; length--)
            list.add(in.readObject());

        return list;
    }

    private Collection createList()
            throws IOException {
        Collection list = null;

        if (_type == null)
            list = new ArrayList();
        else if (!_type.isInterface()) {
            try {
                list = (Collection) _type.newInstance();
            } catch (Exception e) {
            }
        }

        if (list != null) {
        } else if (SortedSet.class.isAssignableFrom(_type))
            list = new TreeSet();
        else if (Set.class.isAssignableFrom(_type))
            list = new HashSet();
        else if (List.class.isAssignableFrom(_type))
            list = new ArrayList();
        else if (Collection.class.isAssignableFrom(_type))
            list = new ArrayList();
        else {
            try {
                list = (Collection) _type.newInstance();
            } catch (Exception e) {
                throw new IOExceptionWrapper(e);
            }
        }

        return list;
    }
}

对以上修改后的代码进行测试,发现在反序列化时ArrayList的子类被识别成了ArrayList,为此还需要对com.alibaba.com.caucho.hessian.io.Hessian2Input 的第21270行做如下修改:

            case 0x77: {
                int length = tag - 0x70;

                String type = readType();

                Deserializer reader;

                /**
                 * Modified by HuQingmiao on 2017-04-01
                 */
                // getListDeserializer(null, cl) -> getListDeserializer(type, cl);
                reader = findSerializerFactory().getListDeserializer(type, cl);

                Object v = reader.readLengthList(this, length);

                return v;
            }

至此, 修改完毕,测试通过。

4. 关于dubbo源码

关于dubbo的bug及修改内容, 见 https://github.com/HuQingmiao/dubbo

© 著作权归作者所有

林中漫步
粉丝 102
博文 55
码字总数 33266
作品 0
深圳
架构师
私信 提问
Soul 网关发布 1.0.4-RELEASE 版本

Soul网关发布1.0.4-RELEASE版本 修复在1.0.3版本的后台管理中,出现的bug。 配置信息序列化方式支持自定义扩展。默认的序列化方式由kroy 改为了java序列化方式。 dubbo框架支持的更改。 对d...

shuaiqiyu
04/09
1K
0
Apache Dubbo 2.6.5 发布,分布式 RPC 服务框架

Apache Dubbo 2.6.5 已发布,包含功能改进、新特性、bug 修复和性能优化。 Enhancements / Features: 重构 @service 的 BeanName 的生成规则 #2235 为 ServiceBean 的导出引入新的 Spring A...

淡漠悠然
2018/11/23
3.5K
9
Dubbo 2.6.5 发布,里程碑版本 2.7.0 前哨

在下一个里程碑版本2.7.0中,Dubbo 将围绕 异步支持优化、元数据改造,支持配置中心,路由规则优化和引入JDK8的特性等方面提升服务调用和服务治理的效率,以及可扩展性。此外还会增强一些功能...

中间件小姐姐
2018/11/27
3.7K
10
Dubbo 升级扩展 --Dubbo-G

Dubbo-G 详细介绍 Dubbo是一个被国内很多互联网公司广泛使用的开源分布式服务框架,即使从国际视野来看应该也是一个非常全面的SOA基础框架。作为一个重要的技术研究课题,在联想电商我们根据...

技术专家
2017/05/26
8.4K
23
Dubbo 2.0.9 发布,阿里巴巴开源服务框架

阿里巴巴开源服务框架Dubbo2.0.9版本发布了,该版本增加了简易监控中心界面,以及修复了一些BUG。 Dubbo首页:http://code.alibabatech.com/wiki/display/dubbo/Home 下载地址:http://code...

红薯
2011/12/14
15.4K
6

没有更多内容

加载失败,请刷新页面

加载更多

IT兄弟连 HTML5教程 HTML5表单 新增的表单属性1

HTML5 Input表单为<form>和<input>标签添加了几个新属性,属性如表1。 1 autocomplete属性 autocomplete属性规定form或input域应该拥有自动完成功能,当用户在自动完成域中开始输入时,浏览器...

老码农的一亩三分地
44分钟前
5
0
OSChina 周五乱弹 —— 葛优理论+1

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @这次装个文艺青年吧 :#今日歌曲推荐# 分享米津玄師的单曲《LOSER》: mv中的舞蹈诡异却又美丽,如此随性怕是难再跳出第二次…… 《LOSER》-...

小小编辑
今天
1K
16
nginx学习笔记

中间件位于客户机/ 服务器的操作系统之上,管理计算机资源和网络通讯。 是连接两个独立应用程序或独立系统的软件。 web请求通过中间件可以直接调用操作系统,也可以经过中间件把请求分发到多...

码农实战
今天
5
0
Spring Security 实战干货:玩转自定义登录

1. 前言 前面的关于 Spring Security 相关的文章只是一个预热。为了接下来更好的实战,如果你错过了请从 Spring Security 实战系列 开始。安全访问的第一步就是认证(Authentication),认证...

码农小胖哥
今天
15
0
JAVA 实现雪花算法生成唯一订单号工具类

import lombok.SneakyThrows;import lombok.extern.slf4j.Slf4j;import java.util.Calendar;/** * Default distributed primary key generator. * * <p> * Use snowflake......

huangkejie
昨天
19
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部