`

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  
不错 挺好的,和我想的差不多。大概都一样。

相关推荐

    基于Java的家庭理财系统设计与开发-金融管理-家庭财产管理-实用性强

    内容概要:文章探讨了互联网时代的背景下开发一个实用的家庭理财系统的重要性。文中分析了国内外家庭理财的现状及存在的问题,阐述了开发此系统的目的——对家庭财产进行一体化管理,提供统计、预测功能。系统涵盖了家庭成员管理、用户认证管理、账单管理等六大功能模块,能够满足用户多方面查询及统计需求,并保证数据的安全性与完整性。设计中运用了先进的技术栈如SSM框架(Spring、SpringMVC、Mybatis),并采用MVC设计模式确保软件结构合理高效。 适用人群:对于希望科学地管理和规划个人或家庭财务的普通民众;从事财务管理相关专业的学生;有兴趣于家政学、经济学等领域研究的专业人士。 使用场景及目标:适用于日常家庭财务管理的各个场景,帮助用户更好地了解自己的消费习惯和资金状况;为目标客户提供一套稳定可靠的解决方案,助力家庭财富增长。 其他说明:文章还包括系统设计的具体方法与技术选型的理由,以及项目实施过程中的难点讨论。对于开发者而言,不仅提供了详尽的技术指南,还强调了用户体验的重要性。

    弹性盒子Flexbox布局.docx

    弹性盒子Flexbox布局.docx

    网络财务系统 SSM毕业设计 附带论文.zip

    网络财务系统 SSM毕业设计 附带论文 启动教程:https://www.bilibili.com/video/BV1GK1iYyE2B

    联想电脑的bios设置

    联想电脑的bios设置、图文都有

    1_教务处关于云南师范大学2024年大学生科研训练基金项目立项申报工作的通知 (1).zip

    1_教务处关于云南师范大学2024年大学生科研训练基金项目立项申报工作的通知 (1).zip

    基于Python实现的自然语言处理大作业-方面情感分析+源代码+文档说明+实验报告

    <项目介绍> 基于Python实现的自然语言处理大作业——方面情感分析+源代码+文档说明+实验报告 - 不懂运行,下载完可以私聊问,可远程教学 该资源内项目源码是个人的毕设,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途

    基于Python的Web安全扫描软件设计与实现

    内容概要:本文探讨了使用Python语言构建一个集成漏洞扫描系统的可能性,旨在解决中小型网络运维人员面临的网络安全挑战。系统采用B/S架构,使用Django框架实现快速开发,Docker容器承载扫描工具Nmap。文中介绍了项目的背景、国内外研究现状、需求分析、系统设计、实现过程和测试结果。 适合人群:初级运维人员和网络安全研究者。 使用场景及目标:系统平台用于检测Web应用程序的安全漏洞,提供轻量级、学习成本低的解决方案,提升网络安全管理水平。 其他说明:虽然系统实现了预期功能,但仍存在一些不足,如架构相对简单、功能单一等问题,未来可以进一步优化。

    【java毕业设计】教学质量评价系统源码(ssm+jsp+mysql+说明文档+LW).zip

    功能说明: 功能:个人中心、公告信息管理、学院管理、学生管理、教师管理、督导管理、教师信息管理、学生评教管理、督导评教管理等功能模块。 环境说明: 开发语言:java 框架:ssm jdk版本:jdk1.8 数据库:mysql 5.7+ 数据库工具:Navicat11+ 管理工具:maven 开发工具:idea/eclipse 部署容器:tomcat7+

    【超强组合】基于VMD-龙格库塔优化算法RUN-Transformer-GRU的光伏预测算研究Matlab实现.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。 替换数据可以直接使用,注释清楚,适合新手

    【小程序+小程序API+后台商城管理+运行指导教程】springboot+mysql实现的供货商城系统

    功能大概包括:商品管理,商品多规格属性管理,商品分类,商品类型,商品品牌,商品订单,会员信息,优惠券,订单管理,加入进货车,立即下单,我的收藏等等 第一:商城后台,功能大概包括:商品管理,商品多规格属性管理,商品分类,商品品牌,商品订单,会员信息等等。 涉及到技术: springboot+Thymeleaf+mybatis 第二:小程序API,涉及到技术: springboot+mybatis-plus+jwt+mapstruct+lombok+redis+swagger 第三:mpvue小程序,小程序主要是用 mpvue 框架开发 mpvue 小程序运行步骤: 1、安装node环境 node 是8版本,这边开发版本是8.12.0,由于mpvue属于老牌小程序框架了,目前这边验证的只是这个node版本, 当然新的小程序UNIAPP发布之后没用此限制拉 2、设置npm淘宝镜像 npm set registry https://registry.npm.taobao.org/ 3、npm install 4、npm run dev

    基于 Java 和 Spring 框架的校园物品维修管理系统的开发与设计

    内容概要:本文详细介绍了基于 Java 和 Spring 框架的校园物品维修管理系统的开发与设计。该系统采用 B/S 架构,主要包括管理员、使用者和维修者三类用户的权限管理。系统提供了用户管理、设备管理和维修管理等功能,通过数据库操作确保数据安全性和系统扩展性。开发中采用了面向对象的设计思想和技术手段,提高了系统的可靠性和用户体验。 适合人群:具有一定 Java 基础的软件开发人员和项目管理者。 使用场景及目标:主要用于高校或研究机构中,对校园内各类设备的使用和维修情况进行高效管理,提升设备使用率和维修效率。 其他说明:系统的设计与实现详细描述了需求分析、总体设计、详细设计以及测试过程,为后续的开发和优化提供了全面的参考。

    【MATLAB代码】二维平面上的TDOA,使用加权最小二乘法,不限制锚点数量(锚点数量&gt;3即可)

    该MATLAB代码使用两步加权最小二乘法通过TDOA技术实现了二维目标定位。它通过随机生成的锚点和目标位置进行模拟,展示了如何通过迭代优化算法来提高位置估计的准确性。代码结构清晰,适合用于理解和实现基于TDOA的定位算法。

    【超强组合】基于VMD-鸽群优化算法PIO-Transformer-LSTM的光伏预测算研究Matlab实现.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。 替换数据可以直接使用,注释清楚,适合新手

    【超强组合】基于VMD-飞蛾扑火优化算法MFO-Transformer-BiLSTM的光伏预测算研究Matlab实现.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。 替换数据可以直接使用,注释清楚,适合新手

    resnet模型-深度学习CNN训练识别蔬菜种类-不含数据集图片-含逐行注释和说明文档.zip

    本代码是基于python pytorch环境安装的。 下载本代码后,有个环境安装的requirement.txt文本 首先是代码的整体介绍 总共是3个py文件,十分的简便 本代码是不含数据集图片的,下载本代码后需要自行搜集图片放到对应的文件夹下即可 需要我们往每个文件夹下搜集来图片放到对应文件夹下,每个对应的文件夹里面也有一张提示图,提示图片放的位置 然后我们需要将搜集来的图片,直接放到对应的文件夹下,就可以对代码进行训练了。 运行01生成txt.py,是将数据集文件夹下的图片路径和对应的标签生成txt格式,划分了训练集和验证集 运行02CNN训练数据集.py,会自动读取txt文本内的内容进行训练,这里是适配了数据集的分类文件夹个数,即使增加了分类文件夹,也不需要修改代码即可训练 训练过程中会有训练进度条,可以查看大概训练的时长,每个epoch训练完后会显示准确率和损失值 训练结束后,会保存log日志,记录每个epoch的准确率和损失值 最后训练的模型会保存在本地名称为model.ckpt 运行03pyqt界面.py,就可以实现自己训练好的模型去识别图片了

    基于“分治法”的排序算法.zip

    基于“分治法”的排序算法

    微信小程序项目开发.docx

    目录 • 一、微信小程序简介 • 二、微信小程序开发准备 • 三、微信小程序开发框架 • 四、微信小程序开发实例 • 六、微信小程序开发进阶  6.1 组件化开发  6.2 API调用  6.3 云开发 • 七、微信小程序开发注意事项  7.1 遵守规范  7.2 注意性能  7.3 保护用户隐私 • 八、总结 大家好,今天将为大家介绍一下微信小程序的开发。微信小程序是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或者搜一下即可打开应用。本文将从浅入深,以轻松易懂的方式为大家介绍微信小程序的开发。 一、微信小程序简介 微信小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具备出色的使用体验。简单来说,微信小程序就是一种可以在微信内运行的应用程序,它的开发成本较低,运行速度快,用户体验良好。 二、微信小程序开发准备 1. 注册微信小程序账号:首先需要在微信公众平台(https://mp.weixin.qq.com/)注册一个小程序账号,完成相关信息的填写和实名认证。 2. 下载安装微信开发者工具:访问微信公众平台,

    Gazebo虚拟环境ROS1工程1.0

    在Gazebo上运行基于ROS1寻路算法

    数据可视化驾驶舱,包含地图,页面可以直接运行

    数据可视化驾驶舱,包含地图,页面可以直接运行

    【超强组合】基于VMD-非洲秃鹫优化算法AVOA-Transformer-BiLSTM的光伏预测算研究Matlab实现.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。 替换数据可以直接使用,注释清楚,适合新手

Global site tag (gtag.js) - Google Analytics