`

Hessian和Java反序列化问题小结

 
阅读更多

Hessian反序列化问题

众所周知,Hessian框架提供的序列化方式,在性能上要优于Java自己的序列化方式。他将对象序列化,生成的字节数组的数量要相对于Java自带的序列化方式要更简洁。

目前公司的一个项目中,有RPC调用的需要,这里我们使用了公司自己的开源RPC框架Dubbo作为远程调用框架,进行业务方法的调用和对象的序列化。这里,我们没有对Dubbo做出特殊配置,Dubbo在Remoting层组件默认的序列化方式就是采用的Hessian协议处理。但是在真正部署测试时,走到需要远程调用的方式时,报出了一下异常(只截取了最核心的异常堆栈):

 

Caused by: com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'com.alibaba.ais.bdc.person.vo.CountVO$CountObject' could not be instantiated
	at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:275)
	at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:155)
	at com.alibaba.com.caucho.hessian.io.SerializerFactory.readObject(SerializerFactory.java:397)
	at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2070)
	at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2005)
	at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1990)
	at com.alibaba.com.caucho.hessian.io.CollectionDeserializer.readLengthList(CollectionDeserializer.java:93)
	at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1678)
	at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:396)
	... 42 more
Caused by: java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
	at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:271)
	... 50 more
Caused by: java.lang.IllegalArgumentException: [Assertion failed] - this argument is required; it must not be null
	at org.springframework.util.Assert.notNull(Assert.java:112)
	at org.springframework.util.Assert.notNull(Assert.java:123)
	at com.alibaba.ais.bdc.person.vo.CountVO$CountObject.<init>(CountVO.java:101)
	... 55 more

从最下面的异常信息可以看出,CountObject这个内部类在对象初始化时,报了参数校验的失败。这个看一下CountObject的出问题的构造函数就一目了然了:

        public CountObject(SimplePerson simplePerson, String imagePrefix){
            Assert.notNull(simplePerson);
            if (StringUtils.isEmpty(imagePrefix)) {
                throw new IllegalArgumentException("imagePrefix [" + imagePrefix + "] is meaningless.");
            }
            this.id = simplePerson.getEmployeeId();
            this.name = simplePerson.getRealName();
            this.imagePath = StringUtils.isEmpty(simplePerson.getImagePathSuffix()) ? null : imagePrefix
                                                                                             + simplePerson.getImagePathSuffix();
        }

现在在构造函数的第一行的Assert就失败了。可是哪里调用这个构造函数导致失败呢?继续网上翻看异常堆栈给出的信息。可以看出在JavaDeserializer.instantiate中抛出了HessianProtocolException异常。进去看一下Hessian这块的源码如下:

  protected Object instantiate()
    throws Exception
  {
    try {
      if (_constructor != null)
	return _constructor.newInstance(_constructorArgs);
      else
	return _type.newInstance();
    } catch (Exception e) {
      throw new HessianProtocolException("'" + _type.getName() + "' could not be instantiated", e);
    }
  }

这里结合上面的异常堆栈可以知道,上面出问题的关键是_constructor_constructorArgs。这两个东东又到底是啥呢?继续来看代码:

  public JavaDeserializer(Class cl)
  {
    _type = cl;
    _fieldMap = getFieldMap(cl);

    _readResolve = getReadResolve(cl);

    if (_readResolve != null) {
      _readResolve.setAccessible(true);
    }

    Constructor []constructors = cl.getDeclaredConstructors();
    long bestCost = Long.MAX_VALUE;

    for (int i = 0; i < constructors.length; i++) {
      Class []param = constructors[i].getParameterTypes();
      long cost = 0;

      for (int j = 0; j < param.length; j++) {
	cost = 4 * cost;

	if (Object.class.equals(param[j]))
	  cost += 1;
	else if (String.class.equals(param[j]))
	  cost += 2;
	else if (int.class.equals(param[j]))
	  cost += 3;
	else if (long.class.equals(param[j]))
	  cost += 4;
	else if (param[j].isPrimitive())
	  cost += 5;
	else
	  cost += 6;
      }

      if (cost < 0 || cost > (1 << 48))
	cost = 1 << 48;

      cost += (long) param.length << 48;
      // _constructor will reference to the constructor with least parameters.
      if (cost < bestCost) {
        _constructor = constructors[i];
        bestCost = cost;
      }
    }

    if (_constructor != null) {
      _constructor.setAccessible(true);
      Class []params = _constructor.getParameterTypes();
      _constructorArgs = new Object[params.length];
      for (int i = 0; i < params.length; i++) {
        _constructorArgs[i] = getParamArg(params[i]);
      }
    }
  }

JavaDeserializer的构造方法中可以看出,这里_constructor会被赋予参数最少的那个构造器。再回过头去看看CountObject的构造器(就上面列出来的那一个),不难看出,这里的_constructor就是上面的那个构造器了。

  /**
   * Creates a map of the classes fields.
   */
  protected static Object getParamArg(Class cl)
  {
    if (! cl.isPrimitive())
      return null;
    else if (boolean.class.equals(cl))
      return Boolean.FALSE;
    else if (byte.class.equals(cl))
      return new Byte((byte) 0);
    else if (short.class.equals(cl))
      return new Short((short) 0);
    else if (char.class.equals(cl))
      return new Character((char) 0);
    else if (int.class.equals(cl))
      return Integer.valueOf(0);
    else if (long.class.equals(cl))
      return Long.valueOf(0);
    else if (float.class.equals(cl))
      return Float.valueOf(0);
    else if (double.class.equals(cl))
      return Double.valueOf(0);
    else
      throw new UnsupportedOperationException();
  }

参看上面的getParamArg方法,就可以知道,由于CountObject唯一的一个构造器的两个参数都不是基本类型,所以这里_constructorArgs所包含的值全部是null。

OK,到这里,上面的异常就搞清楚了,Hessian反序列化时,使用反射调用构造函数生成对象时,传入的参数不合法,造成了上面的异常。知道了原因,解决的方法也很简单,就是添加了一个无参的构造器给CountObject,于是上面的问题就解决了。。。

这里,需要注意的是,如果序列化机制使用的是Hessian,序列化的对象又没有提供默认的无参构造器时,需要注意上面类似的问题了。

Java本身反序列化问题

Java本身的反序列化机制虽然性能稍差一些,但本身使用的约束条件相对却要宽松一些,其实只要满足下面两条,一个类对象就是可以完美支持序列化机制了:


  1. 类实现java.io.Serializable接口。
  2. 类包含的所有属性都是实现了java.io.Serializable接口的,或者被标记为了transient。

对于构造函数本身没有任何约束。这里,Java序列化本身其实也是和new以及Java反射机制“平级”的实例化对象的方式。所以,对于单例模式的场景,还是需要考虑是否会有序列化因素造成的单例失效(因为他实例化对象不依赖于构造器,所以一个private的构造器显然没法阻止他的“胡作非为”)。当然,对于这种情况,也可以自己实现下面的方法:

 

private Object readResolve()

通过实现上面的方法,自己可以在其中明确指定,放回的对象的实例是哪一个。但对于通过如上方式保证的单例模式至少需要注意一下两点:


  1. readResolve方法的可见性(public/protected/private)问题:因为如果这个方法不是private的,就有可能被起子类直接继承过去。这可能造成你在反序列化子类对象时出错(因为这个方法返回了父类的某个固定的对象)。
  2. 使用readResolve方法时,往往比较容易返回某个固定的对象。但这其实和真正的对象反序列化其实是有点矛盾的。因为你反序列化对象时,多数场景都是希望恢复原来的对象的“状态”,而不是固定的某个对象。所以只要你的类内的属性有没有被标识成transient的,就要格外小心了。

鉴于上面所说的稍微复杂的现象,如果单纯的考虑单例的需要,更好的方式是通过枚举来实现,因为枚举至少可以在JVM层面,帮你保证每个枚举实例一定是单例的,即使使用反序列化机制,也无法绕过这个限制,所以可以帮你省不少心。

好了,上面扯的有点远了,关于Java本身的序列化机制,下面写了一个简单的把对象序列化成字节数组,再由字节数组反序列化回来的例子,看完之后应该会更明了一些:

 

public class Person implements Serializable {

    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
    }

    private static class Employee extends Person{

        String title;

        private Employee(String name, int age, String title) {
            super(name, age);
            this.title = title;
        }

        @Override
        public String toString() {
            return "Employee{" + "name='" + name + '\'' +
                ", age=" + age + '\'' +
                ", title='" + title + '\'' +
                '}';
        }
    }

    public static void main(String[] args) {
        byte[] bytes;
        Person person1 = new Person( "test1",20 );
        Person person2;
        Employee employee1 = new Employee( "employee1",25,"Manager" );
        Employee employee2;

        ByteArrayOutputStream byteOutputStream = null;
        ObjectOutputStream objectOutputStream = null;

        ByteArrayInputStream byteArrayInputStream = null;
        ObjectInputStream objectInputStream = null;

        try {
            //generate byteArray.
            byteOutputStream = new ByteArrayOutputStream( );
            objectOutputStream = new ObjectOutputStream( byteOutputStream);
            //serialize person1
            objectOutputStream.writeObject( person1 );
            //serialize employee1
            objectOutputStream.writeObject( employee1 );

            bytes = byteOutputStream.toByteArray();

            for (byte aByte : bytes) {
                System.out.print(aByte);
            }
            System.out.println();
            System.out.println("Bytes's length is :"+bytes.length);

            //generate Object from byteArray.
            byteArrayInputStream = new ByteArrayInputStream( bytes );
            objectInputStream = new ObjectInputStream( byteArrayInputStream );
            //deserialize person1
            person2 = (Person)objectInputStream.readObject();
            //deserialize employee1
            employee2 = (Employee)objectInputStream.readObject();
            System.out.println("person2 got from byteArray is : "+person2);
            System.out.println("employee2 got from byteArray is : "+employee2);

            System.out.println("person1's memory id :"+Integer.toHexString(person1.hashCode()));
            System.out.println("person2's memory id :"+Integer.toHexString(person2.hashCode()));
            System.out.println("employee1's memory id :"+Integer.toHexString(employee1.hashCode()));
            System.out.println("employee2's memory id :"+Integer.toHexString(employee2.hashCode()));

        } catch (IOException e) {
            e.printStackTrace();
        }catch ( ClassNotFoundException ce ){
            ce.printStackTrace();
        }
        finally {
            try {
                byteOutputStream.close();
                objectOutputStream.close();
                byteArrayInputStream.close();
                objectInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

上面代码执行的结果如下:

-84-19051151140329911110946115107121461191191194611510111410597108105122971161051111104680101114115111110-97-123-26-11-111120-40-115202730397103101760411097109101116018761069711897471089711010347831161141051101035912011200020116051161011151164911511404199111109461151071214611911911946115101114105971081051229711610511111046801011141151111103669109112108111121101101-11-66110-28-62-10611536201760511610511610810111301260112011301260000025116091011091121081111211011014911607779711097103101114
Bytes's length is :200
person2 got from byteArray is : Person{name='test1', age=20}
employee2 got from byteArray is : Employee{name='employee1', age=25', title='Manager'}
person1's memory id :29173ef
person2's memory id :96fa474
employee1's memory id :6c121f1d
employee2's memory id :95c083

最后再补充一个Java序列化规范的地址,有时间时再细读一下:http://docs.oracle.com/javase/7/docs/platform/serialization/spec/serial-arch.html  

     

分享到:
评论
2 楼 chembo 2015-03-03  
读君一博,解我一周愁
1 楼 fanjicaho 2012-10-06  
不错 挺好的,和我想的差不多。大概都一样。

相关推荐

    Hessian 使用小结

    4. **序列化**:由于Hessian是基于二进制的,因此传输的对象需要实现`Serializable`接口,以确保它们能被正确序列化和反序列化。 5. **客户端调用**:客户端通过生成服务接口的代理对象来调用服务端的方法,这些...

    Spring in Action(第二版 中文高清版).part2

    第一部分 Spring的核心 第1章 开始Spring之旅 1.1 Spring是什么 1.2 开始Spring之旅 1.3 理解依赖注入 1.3.1 依赖注入 1.3.2 DI应用 1.3.3 企业级应用中的依赖注入 1.4 应用AOP ...B.4 小结

    Spring in Action(第二版 中文高清版).part1

    第一部分 Spring的核心 第1章 开始Spring之旅 1.1 Spring是什么 1.2 开始Spring之旅 1.3 理解依赖注入 1.3.1 依赖注入 1.3.2 DI应用 1.3.3 企业级应用中的依赖注入 1.4 应用AOP ...B.4 小结

    Spring in Action(第2版)中文版

    目录 第一部分spring的核心 第1章开始spring之旅 1.1spring是什么 1.2开始spring之旅 1.3理解依赖注入 ...1.5小结 ...2.4.2混合使用自动和手动装配 ...2.5.3初始化和销毁bean ...2.6小结 ...3.1声明父bean和子bean ...b.4小结

    电力系统中基于MATLAB的价格型需求响应与电价弹性矩阵优化

    内容概要:本文详细介绍了如何利用MATLAB进行价格型需求响应的研究,特别是电价弹性矩阵的构建与优化。文章首先解释了电价弹性矩阵的概念及其重要性,接着展示了如何通过MATLAB代码实现弹性矩阵的初始化、负荷变化量的计算以及优化方法。文中还讨论了如何通过非线性约束和目标函数最小化峰谷差,确保用户用电舒适度的同时实现负荷的有效调节。此外,文章提供了具体的代码实例,包括原始负荷曲线与优化后负荷曲线的对比图,以及基于历史数据的参数优化方法。 适合人群:从事电力系统优化、能源管理及相关领域的研究人员和技术人员。 使用场景及目标:适用于希望深入了解并掌握价格型需求响应机制的专业人士,旨在帮助他们更好地理解和应用电价弹性矩阵,优化电力系统的负荷分布,提高能源利用效率。 其他说明:文章强调了实际应用中的注意事项,如弹性矩阵的动态校准和用户价格敏感度的滞后效应,提供了实用的技术细节和实践经验。

    一级医院医疗信息管理系统安装调试技术服务合同20240801.pdf

    一级医院医疗信息管理系统安装调试技术服务合同20240801.pdf

    表5 文献综述.doc

    表5 文献综述.doc

    36W低压输入正激电源, 正激变压器设计方法步骤及谐振电感的设计方法,主要讲诉了正激电源变压器测的输入输出参数,按输入的条件设计相关的变压器的参数,同时将输出电感的设计方法一并例出,详细的设计步骤

    36W低压输入正激电源 变压器电感设计

    基于YOLOv8的深度学习课堂行为检测系统源码(含检测图片和视频)

    基于YOLOv8的深度学习课堂行为检测系统源码,软件开发环境python3.9,系统界面开发pyqt5。在使用前安装python3.9,并安装软件所需的依赖库,直接运行MainProgram.py文件即可打开程序。模型训练时,将train,val数据集的绝对路径改为自己项目数据集的绝对路径,运行train.py文件即可开始进行模型训练,内含项目文件说明,以及检测图片和视频。

    odbc-oracle zabbix模版原版

    odbc_oracle zabbix模版原版

    基于纳什谈判理论的风光氢多主体能源系统合作运行方法——综合能源交易与优化模型

    内容概要:本文探讨了利用纳什谈判理论来优化风光氢多主体能源系统的合作运行方法。通过MATLAB代码实现了一个复杂的优化模型,解决了风电、光伏和氢能之间的合作问题。文中详细介绍了ADMM(交替方向乘子法)框架的应用,包括联盟效益最大化和收益分配谈判两个子任务。此外,还涉及了加权残差计算、目标函数构造、可视化工具以及多种博弈模式的对比等功能模块。实验结果显示,合作模式下系统总成本显著降低,氢能利用率大幅提升。 适合人群:从事能源系统研究的专业人士、对博弈论及其应用感兴趣的学者和技术人员。 使用场景及目标:适用于需要优化多主体能源系统合作运行的场合,如工业园区、电网公司等。主要目标是提高能源利用效率,降低成本,增强系统的灵活性和稳定性。 其他说明:代码中包含了丰富的可视化工具,能够帮助研究人员更好地理解和展示谈判过程及结果。同时,提供了多种博弈模式的对比功能,便于进行性能评估和方案选择。

    C#与Halcon联合编程实现高效视觉几何定位与测量框架

    内容概要:本文详细介绍了如何利用C#与Halcon联合编程构建高效的视觉几何定位与测量框架。主要内容涵盖模板创建与匹配、圆测量、数据持久化以及图像采集等方面的技术细节。首先,通过创建形状模板并进行匹配,实现了工件的精确定位。接着,针对圆形物体的测量,提出了动态ROI绘制、亚像素边缘提取和稳健圆拟合的方法。此外,还讨论了模板管理和图像采集的最佳实践,确保系统的稳定性和高效性。最后,强调了Halcon对象的内存管理和错误处理机制,提供了实用的优化建议。 适合人群:具备一定编程基础,尤其是对C#和Halcon有一定了解的研发人员和技术爱好者。 使用场景及目标:适用于工业生产线上的自动化检测设备开发,旨在提高工件定位和尺寸测量的精度与效率。主要目标是帮助开发者掌握C#与Halcon联合编程的具体实现方法,从而构建稳定可靠的视觉检测系统。 其他说明:文中提供了大量实战代码片段和调试技巧,有助于读者快速理解和应用相关技术。同时,作者分享了许多实际项目中的经验和教训,使读者能够避开常见陷阱,提升开发效率。

    QT6 C++视频播放器实现(基于QGraphicsVideo)

    QT视频播放器实现(基于QGraphicsView)

    评估管线钢环焊缝质量及其对氢脆的敏感性.pptx

    评估管线钢环焊缝质量及其对氢脆的敏感性.pptx

    机器学习(预测模型):专注于 2024 年出现的漏洞(CVE)信息数据集

    该是一个在 Kaggle 上发布的数据集,专注于 2024 年出现的漏洞(CVE)信息。以下是关于该数据集的详细介绍:该数据集收集了 2024 年记录在案的各类漏洞信息,涵盖了漏洞的利用方式(Exploits)、通用漏洞评分系统(CVSS)评分以及受影响的操作系统(OS)。通过整合这些信息,研究人员和安全专家可以全面了解每个漏洞的潜在威胁、影响范围以及可能的攻击途径。数据主要来源于权威的漏洞信息平台,如美国国家漏洞数据库(NVD)等。这些数据经过整理和筛选后被纳入数据集,确保了信息的准确性和可靠性。数据集特点:全面性:涵盖了多种操作系统(如 Windows、Linux、Android 等)的漏洞信息,反映了不同平台的安全状况。实用性:CVSS 评分提供了漏洞严重程度的量化指标,帮助用户快速评估漏洞的优先级。同时,漏洞利用信息(Exploits)为安全研究人员提供了攻击者可能的攻击手段,有助于提前制定防御策略。时效性:专注于 2024 年的漏洞数据,反映了当前网络安全领域面临的新挑战和新趋势。该数据集可用于多种研究和实践场景: 安全研究:研究人员可以利用该数据集分析漏洞的分布规律、攻击趋势以及不同操作系统之间的安全差异,为网络安全防护提供理论支持。 机器学习与数据分析:数据集中的结构化信息适合用于机器学习模型的训练,例如预测漏洞的 CVSS 评分、识别潜在的高危漏洞等。 企业安全评估:企业安全团队可以参考该数据集中的漏洞信息,结合自身系统的实际情况,进行安全评估和漏洞修复计划的制定。

    QML Combobox 自动过滤,输入字符串后自动匹配

    博客主页:https://blog.csdn.net/luoyayun361 QML ComboBox控件,输入关键字后自动过滤包含关键字的列表,方便快速查找列表项

    【人工智能领域】人工智能技术发展历程、核心原理及应用指南:涵盖机器学习、深度学习、NLP和计算机视觉的全面介绍

    内容概要:本文全面介绍了人工智能技术的发展历程、核心技术原理、应用方法及其未来趋势。首先阐述了人工智能的定义和核心目标,随后按时间顺序回顾了其从萌芽到爆发的五个发展阶段。接着详细讲解了机器学习、深度学习、自然语言处理和计算机视觉等核心技术原理,并介绍了使用现成AI服务和开发自定义AI模型的应用方法。此外,还展示了智能客服系统、图像分类应用和智能推荐系统的具体实现案例。针对普通用户,提供了使用大模型的指南和提问技巧,强调了隐私保护、信息验证等注意事项。最后展望了多模态AI、可解释AI等未来发展方向,并推荐了相关学习资源。; 适合人群:对人工智能感兴趣的初学者、技术人员以及希望了解AI技术应用的普通大众。; 使用场景及目标:①帮助初学者快速了解AI的基本概念和发展脉络;②为技术人员提供核心技术原理和应用方法的参考;③指导普通用户如何有效地使用大模型进行日常查询和任务处理。; 其他说明:本文不仅涵盖了AI技术的基础知识,还提供了丰富的实际应用案例和实用技巧,旨在帮助读者全面理解人工智能技术,并能在实际工作中加以应用。同时提醒读者关注AI伦理和版权问题,确保安全合法地使用AI工具。

    本学习由 Matrix 工作室制作并开发,包括算法与数据结构的学习路线和各种题解

    本学习由 Matrix 工作室制作并开发,包括算法与数据结构的学习路线和各种题解。

    基于智慧图书馆基础业务流程Axure11高保真原型设计

    本项目致力于构建基于微服务架构的智慧图书馆管理平台,重点突破多校区图书馆异构系统间的数据壁垒。通过建立统一数据治理规范、部署智能分析模块、重构业务流程引擎,系统性实现以下建设目标:构建跨馆业务数据的标准化整合通道,实施容器化部署的弹性资源管理体系,开发具备机器学习能力的业务辅助决策系统,打造可量化评估的管理效能提升模型,最终形成支持PB级数据处理的分布式存储体系与全维度数据资产图谱。

    mysql中慢sql分析

    根据processlist查询出慢sql 1.修改配置文件中的mysql链接 2.目前是15秒执行一次获取执行时间在5秒上的sql,可以在配置中修改 3.执行后查出的慢sql会记录到log文件夹中以日期命名的txt文件中,可自行查验

Global site tag (gtag.js) - Google Analytics