from:http://hittyt.iteye.com/blog/1691772
Hessian反序列化問題
眾所周知,Hessian框架提供的序列化方式,在性能上要優于Java自己的序列化方式。他將對象序列化,生成的字節數組的數量要相對于Java自帶的序列化方式要更簡潔。
目前公司的一個項目中,有RPC調用的需要,這里我們使用了公司自己的開源RPC框架Dubbo作為遠程調用框架,進行業務方法的調用和對象的序列化。這里,我們沒有對Dubbo做出特殊配置,Dubbo在Remoting層組件默認的序列化方式就是采用的Hessian協議處理。但是在真正部署測試時,走到需要遠程調用的方式時,報出了一下異常(只截取了最核心的異常堆棧):
Java代碼

- 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的出問題的構造函數就一目了然了:
Java代碼

- 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這塊的源碼如下:
Java代碼

- protected Object instantiate()
- throws Exception
- {
- try {
- if (_constructor != null)
- eturn _constructor.newInstance(_constructorArgs);
- else
- eturn _type.newInstance();
- } catch (Exception e) {
- throw new HessianProtocolException("'" + _type.getName() + "' could not be instantiated", e);
- }
- }
這里結合上面的異常堆棧可以知道,上面出問題的關鍵是_constructor和_constructorArgs。這兩個東東又到底是啥呢?繼續來看代碼:
Java代碼

- 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就是上面的那個構造器了。
Java代碼

- /**
- * 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本身的反序列化機制雖然性能稍差一些,但本身使用的約束條件相對卻要寬松一些,其實只要滿足下面兩條,一個類對象就是可以完美支持序列化機制了:
- 類實現java.io.Serializable接口。
- 類包含的所有屬性都是實現了java.io.Serializable接口的,或者被標記為了transient。
對于構造函數本身沒有任何約束。這里,Java序列化本身其實也是和new以及Java反射機制“平級”的實例化對象的方式。所以,對于單例模式的場景,還是需要考慮是否會有序列化因素造成的單例失效(因為他實例化對象不依賴于構造器,所以一個private的構造器顯然沒法阻止他的“胡作非為”)。當然,對于這種情況,也可以自己實現下面的方法:
Java代碼

- private Object readResolve()
通過實現上面的方法,自己可以在其中明確指定,放回的對象的實例是哪一個。但對于通過如上方式保證的單例模式至少需要注意一下兩點:
- readResolve方法的可見性(public/protected/private)問題:因為如果這個方法不是private的,就有可能被起子類直接繼承過去。這可能造成你在反序列化子類對象時出錯(因為這個方法返回了父類的某個固定的對象)。
- 使用readResolve方法時,往往比較容易返回某個固定的對象。但這其實和真正的對象反序列化其實是有點矛盾的。因為你反序列化對象時,多數場景都是希望恢復原來的對象的“狀態”,而不是固定的某個對象。所以只要你的類內的屬性有沒有被標識成transient的,就要格外小心了。
鑒于上面所說的稍微復雜的現象,如果單純的考慮單例的需要,更好的方式是通過枚舉來實現,因為枚舉至少可以在JVM層面,幫你保證每個枚舉實例一定是單例的,即使使用反序列化機制,也無法繞過這個限制,所以可以幫你省不少心。
好了,上面扯的有點遠了,關于Java本身的序列化機制,下面寫了一個簡單的把對象序列化成字節數組,再由字節數組反序列化回來的例子,看完之后應該會更明了一些:
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();
- }
- }
- }
- }
上面代碼執行的結果如下:
Java代碼

- -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