问题
最近在dubbo接口扩展上遇到了问题。dubbo的参数及返回对象,肯定是要可序列化的,即实现Serializable接口。需求是需要在接口参数中,加入一个字段,但是担心对原来的consumer产生影响,因此对java序列化进行了一下梳理测试。
顺便说下关于dubbo接口扩展碰到的这个问题,有几点收获:
接口的传参,尽量用对象代替多个简单类型的参数,后者不便于加参数
返回数据,同样尽量用对象代替简单类型
更好的参数或返回数据扩展方案,应该是采用继承原有参数或返回类型的方式
序列化
java序列化,就是将java对象序列化为字节流,可以进行传递或者保存,在使用方对结果进行反序列化,从而获取到原来对象的属性值。
在需要将内存中对象保存到文件,或者直接传输对象时,会用到序列化。dubbo就是在provider和consumer之间传递对象数据。
类定义改变
回到最初的问题,其根本是java类定义在发送方发生改变后,接收方能否正确反序列化数据。
单元测试代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
class Person implements Serializable {
private String name ;
public String getName () {
return name ;
}
public void setName ( String name ) {
this . name = name ;
}
}
class NewPerson implements Serializable {
private String name ;
public String getName () {
return name ;
}
public void setName ( String name ) {
this . name = name ;
}
}
class ChildPerson extends Person {
private int age ;
public int getAge () {
return age ;
}
public void setAge ( int age ) {
this . age = age ;
}
}
@Test
public void testSerialize () throws IOException , ClassNotFoundException {
Person parent = new Person ();
parent . setName ( "Parent" );
String parentSerFile = "parent.ser" ;
String childSerFile = "child.ser" ;
// 序列化parent到parentSerFile
serialize ( parent , parentSerFile );
Person person = unSerialize ( parentSerFile );
System . out . println ( person . getName ()); // OK
// NewPerson newPerson = unSerialize(parentSerFile); // java.lang.ClassCastException
// System.out.println(newPerson.getName());
// 序列化child到childSerFile
ChildPerson child = new ChildPerson ();
child . setName ( "Child" );
child . setAge ( 10 );
serialize ( child , childSerFile );
person = unSerialize ( childSerFile );
System . out . println ( person . getName ()); // OK
System . out . println ((( ChildPerson ) person ). getAge ());
}
/**
* 序列化对象,保存到文件
*/
private void serialize ( Object obj , String fileName ) throws IOException {
FileOutputStream fs = new FileOutputStream ( fileName );
ObjectOutputStream os = new ObjectOutputStream ( fs );
os . writeObject ( obj );
os . close ();
}
/**
* 反序列化对象
*/
private < T > T unSerialize ( String fileName ) throws IOException , ClassNotFoundException {
ObjectInputStream inputStream = new ObjectInputStream ( new FileInputStream ( fileName ));
T obj = ( T ) inputStream . readObject ();
inputStream . close ();
return obj ;
}
类路径及类名必须一致
发送方类com.test1.Class1,接收方是com.test1.Class2或者com.test2.Class1,都不能正确反序列化,报java.lang.ClassCastException异常,如上述代码中注掉的newPerson部分。
类定义发生改变
若类定义发生改变,即发送方和接收方,虽然类路径和类名一致,但是发送方和接收方的类版本等不一致,此时亦会报错。
如上述代码,在序列化生成parent.ser文件后,将Person类添加字段sex,如下:
1
2
3
4
5
6
class Person implements Serializable {
private String name ;
private String sex ;
...
然后进行反序列化,会抛出异常java.io.InvalidClassException。提示:local class incompatible: stream classdesc serialVersionUID = 4485681234731380735, local class serialVersionUID = 5015652288950510004
这就是serialVersionUID的作用了。在Person的类定义中,加入以下代码就OK了:
1
private static final long serialVersionUID = 4485681234731380735L ;
因此,在定义可序列化对象的时候,强烈建议显式定义serialVersionUID,防止类由于版本等的问题,无法匹配从而无法反序列化问题。
在Eclipse中,是会自动提示生成随机serialVersionUID的,在Intellij idea中同样可以,需要开启:File | Settings | Editor | Inspections 中 Java | Serialization issues | Serializable class without ‘serialVersionUID’ , 开启后,用alt+enter就可以显式生成serialVersionUID了。
更好的扩展方式
更好的扩展方式,应该是定义可序列化对象类的子类,将子类对象序列化后,仍然可以反序列化为原父类对象,从而对原来的序列化数据接收方无影响。
如上述测试代码中的将ChildPerson类的对象序列化后的文件,反序列化为Person类对象。
在一个已成熟稳定的系统中,扩展的时候,应尽量采用这种方式;但由于我们的系统刚刚搭建完成,因此我直接显式声明了serialVersionUID