from:http://hittyt.iteye.com/blog/1691772

Hessian反序列化问题

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

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

 

Java代码  收藏代码
  1. Caused by: com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'com.alibaba.ais.bdc.person.vo.CountVO$CountObject' could not be instantiated  
  2.     at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:275)  
  3.     at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:155)  
  4.     at com.alibaba.com.caucho.hessian.io.SerializerFactory.readObject(SerializerFactory.java:397)  
  5.     at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2070)  
  6.     at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2005)  
  7.     at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1990)  
  8.     at com.alibaba.com.caucho.hessian.io.CollectionDeserializer.readLengthList(CollectionDeserializer.java:93)  
  9.     at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1678)  
  10.     at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:396)  
  11.     ... 42 more  
  12. Caused by: java.lang.reflect.InvocationTargetException  
  13.     at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)  
  14.     at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)  
  15.     at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)  
  16.     at java.lang.reflect.Constructor.newInstance(Constructor.java:513)  
  17.     at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:271)  
  18.     ... 50 more  
  19. Caused by: java.lang.IllegalArgumentException: [Assertion failed] - this argument is required; it must not be null  
  20.     at org.springframework.util.Assert.notNull(Assert.java:112)  
  21.     at org.springframework.util.Assert.notNull(Assert.java:123)  
  22.     at com.alibaba.ais.bdc.person.vo.CountVO$CountObject.<init>(CountVO.java:101)  
  23.     ... 55 more  

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

Java代码  收藏代码
  1. public CountObject(SimplePerson simplePerson, String imagePrefix){  
  2.     Assert.notNull(simplePerson);  
  3.     if (StringUtils.isEmpty(imagePrefix)) {  
  4.         throw new IllegalArgumentException("imagePrefix [" + imagePrefix + "] is meaningless.");  
  5.     }  
  6.     this.id = simplePerson.getEmployeeId();  
  7.     this.name = simplePerson.getRealName();  
  8.     this.imagePath = StringUtils.isEmpty(simplePerson.getImagePathSuffix()) ? null : imagePrefix  
  9.                                                                                      + simplePerson.getImagePathSuffix();  
  10. }  

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

Java代码  收藏代码
  1. protected Object instantiate()  
  2.   throws Exception  
  3. {  
  4.   try {  
  5.     if (_constructor != null)  
  6. eturn _constructor.newInstance(_constructorArgs);  
  7.     else  
  8. eturn _type.newInstance();  
  9.   } catch (Exception e) {  
  10.     throw new HessianProtocolException("'" + _type.getName() + "' could not be instantiated", e);  
  11.   }  
  12. }  

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

Java代码  收藏代码
  1.  public JavaDeserializer(Class cl)  
  2.  {  
  3.    _type = cl;  
  4.    _fieldMap = getFieldMap(cl);  
  5.   
  6.    _readResolve = getReadResolve(cl);  
  7.   
  8.    if (_readResolve != null) {  
  9.      _readResolve.setAccessible(true);  
  10.    }  
  11.   
  12.    Constructor []constructors = cl.getDeclaredConstructors();  
  13.    long bestCost = Long.MAX_VALUE;  
  14.   
  15.    for (int i = 0; i < constructors.length; i++) {  
  16.      Class []param = constructors[i].getParameterTypes();  
  17.      long cost = 0;  
  18.   
  19.      for (int j = 0; j < param.length; j++) {  
  20. cost = 4 * cost;  
  21.   
  22. if (Object.class.equals(param[j]))  
  23.   cost += 1;  
  24. else if (String.class.equals(param[j]))  
  25.   cost += 2;  
  26. else if (int.class.equals(param[j]))  
  27.   cost += 3;  
  28. else if (long.class.equals(param[j]))  
  29.   cost += 4;  
  30. else if (param[j].isPrimitive())  
  31.   cost += 5;  
  32. else  
  33.   cost += 6;  
  34.      }  
  35.   
  36.      if (cost < 0 || cost > (1 << 48))  
  37. cost = 1 << 48;  
  38.   
  39.      cost += (long) param.length << 48;  
  40.      // _constructor will reference to the constructor with least parameters.  
  41.      if (cost < bestCost) {  
  42.        _constructor = constructors[i];  
  43.        bestCost = cost;  
  44.      }  
  45.    }  
  46.   
  47.    if (_constructor != null) {  
  48.      _constructor.setAccessible(true);  
  49.      Class []params = _constructor.getParameterTypes();  
  50.      _constructorArgs = new Object[params.length];  
  51.      for (int i = 0; i < params.length; i++) {  
  52.        _constructorArgs[i] = getParamArg(params[i]);  
  53.      }  
  54.    }  
  55.  }  

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

Java代码  收藏代码
  1. /** 
  2.  * Creates a map of the classes fields. 
  3.  */  
  4. protected static Object getParamArg(Class cl)  
  5. {  
  6.   if (! cl.isPrimitive())  
  7.     return null;  
  8.   else if (boolean.class.equals(cl))  
  9.     return Boolean.FALSE;  
  10.   else if (byte.class.equals(cl))  
  11.     return new Byte((byte0);  
  12.   else if (short.class.equals(cl))  
  13.     return new Short((short0);  
  14.   else if (char.class.equals(cl))  
  15.     return new Character((char0);  
  16.   else if (int.class.equals(cl))  
  17.     return Integer.valueOf(0);  
  18.   else if (long.class.equals(cl))  
  19.     return Long.valueOf(0);  
  20.   else if (float.class.equals(cl))  
  21.     return Float.valueOf(0);  
  22.   else if (double.class.equals(cl))  
  23.     return Double.valueOf(0);  
  24.   else  
  25.     throw new UnsupportedOperationException();  
  26. }  

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

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

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

Java本身反序列化问题

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


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

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

 

Java代码  收藏代码
  1. private Object readResolve()  

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


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

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

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

 

Java代码  收藏代码
  1. public class Person implements Serializable {  
  2.   
  3.     String name;  
  4.     int age;  
  5.   
  6.     public Person(String name, int age) {  
  7.         this.name = name;  
  8.         this.age = age;  
  9.     }  
  10.   
  11.     @Override  
  12.     public String toString() {  
  13.         return "Person{" +  
  14.             "name='" + name + '\'' +  
  15.             ", age=" + age +  
  16.             '}';  
  17.     }  
  18.   
  19.     private static class Employee extends Person{  
  20.   
  21.         String title;  
  22.   
  23.         private Employee(String name, int age, String title) {  
  24.             super(name, age);  
  25.             this.title = title;  
  26.         }  
  27.   
  28.         @Override  
  29.         public String toString() {  
  30.             return "Employee{" + "name='" + name + '\'' +  
  31.                 ", age=" + age + '\'' +  
  32.                 ", title='" + title + '\'' +  
  33.                 '}';  
  34.         }  
  35.     }  
  36.   
  37.     public static void main(String[] args) {  
  38.         byte[] bytes;  
  39.         Person person1 = new Person( "test1",20 );  
  40.         Person person2;  
  41.         Employee employee1 = new Employee( "employee1",25,"Manager" );  
  42.         Employee employee2;  
  43.   
  44.         ByteArrayOutputStream byteOutputStream = null;  
  45.         ObjectOutputStream objectOutputStream = null;  
  46.   
  47.         ByteArrayInputStream byteArrayInputStream = null;  
  48.         ObjectInputStream objectInputStream = null;  
  49.   
  50.         try {  
  51.             //generate byteArray.  
  52.             byteOutputStream = new ByteArrayOutputStream( );  
  53.             objectOutputStream = new ObjectOutputStream( byteOutputStream);  
  54.             //serialize person1  
  55.             objectOutputStream.writeObject( person1 );  
  56.             //serialize employee1  
  57.             objectOutputStream.writeObject( employee1 );  
  58.   
  59.             bytes = byteOutputStream.toByteArray();  
  60.   
  61.             for (byte aByte : bytes) {  
  62.                 System.out.print(aByte);  
  63.             }  
  64.             System.out.println();  
  65.             System.out.println("Bytes's length is :"+bytes.length);  
  66.   
  67.             //generate Object from byteArray.  
  68.             byteArrayInputStream = new ByteArrayInputStream( bytes );  
  69.             objectInputStream = new ObjectInputStream( byteArrayInputStream );  
  70.             //deserialize person1  
  71.             person2 = (Person)objectInputStream.readObject();  
  72.             //deserialize employee1  
  73.             employee2 = (Employee)objectInputStream.readObject();  
  74.             System.out.println("person2 got from byteArray is : "+person2);  
  75.             System.out.println("employee2 got from byteArray is : "+employee2);  
  76.   
  77.             System.out.println("person1's memory id :"+Integer.toHexString(person1.hashCode()));  
  78.             System.out.println("person2's memory id :"+Integer.toHexString(person2.hashCode()));  
  79.             System.out.println("employee1's memory id :"+Integer.toHexString(employee1.hashCode()));  
  80.             System.out.println("employee2's memory id :"+Integer.toHexString(employee2.hashCode()));  
  81.   
  82.         } catch (IOException e) {  
  83.             e.printStackTrace();  
  84.         }catch ( ClassNotFoundException ce ){  
  85.             ce.printStackTrace();  
  86.         }  
  87.         finally {  
  88.             try {  
  89.                 byteOutputStream.close();  
  90.                 objectOutputStream.close();  
  91.                 byteArrayInputStream.close();  
  92.                 objectInputStream.close();  
  93.             } catch (IOException e) {  
  94.                 e.printStackTrace();  
  95.             }  
  96.         }  
  97.     }  
  98. }  

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

Java代码  收藏代码
  1. -84-19051151140329911110946115107121461191191194611510111410597108105122971161051111104680101114115111110-97-123-26-11-111120-40-115202730397103101760411097109101116018761069711897471089711010347831161141051101035912011200020116051161011151164911511404199111109461151071214611911911946115101114105971081051229711610511111046801011141151111103669109112108111121101101-11-66110-28-62-10611536201760511610511610810111301260112011301260000025116091011091121081111211011014911607779711097103101114  
  2. Bytes's length is :200  
  3. person2 got from byteArray is : Person{name='test1', age=20}  
  4. employee2 got from byteArray is : Employee{name='employee1', age=25', title='Manager'}  
  5. person1's memory id :29173ef  
  6. person2's memory id :96fa474  
  7. employee1's memory id :6c121f1d  
  8. employee2's memory id :95c083  

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