Java Serializable系列化与反系列化.docx
- 文档编号:24640467
- 上传时间:2023-05-29
- 格式:DOCX
- 页数:18
- 大小:21.50KB
Java Serializable系列化与反系列化.docx
《Java Serializable系列化与反系列化.docx》由会员分享,可在线阅读,更多相关《Java Serializable系列化与反系列化.docx(18页珍藏版)》请在冰豆网上搜索。
JavaSerializable系列化与反系列化
【引言】
将Java对象序列化为二进制文件的Java序列化技术是Java系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现Serializable接口,使用ObjectInputStream和ObjectOutputStream进行对象的读写。
然而在有些情况下,光知道这些还远远不够,文章列举了笔者遇到的一些真实情境,它们与Java序列化相关,通过分析情境出现的原因,使读者轻松牢记Java序列化中的一些高级认识。
【系列化serialVersionUID问题】
在Java系列化与反系列化中,虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化ID是否一致(就是privatestaticfinallongserialVersionUID=1L),如果serialVersionUID不同,你将得到一个java.io.InvalidClassException,看如下代码:
[java]viewplaincopyprint?
1packagewen.hui.test.serializable;
2
3importjava.io.Serializable;
4
5/**
6*serializable测试
7*
8*@authorwhwang
9*2011-12-1下午09:
50:
07
10*/
11publicclassAimplementsSerializable{
12
13privatestaticfinallongserialVersionUID=2L;
14
15publicA(){
16
17}
18
19publicvoidprint(){
20System.err.println("testserializable");
21}
22
23publicstaticvoidmain(String[]args)throwsException{
24
25}
26}
[java]viewplaincopyprint?
27packagewen.hui.test.serializable;
28
29importjava.io.FileInputStream;
30importjava.io.FileOutputStream;
31importjava.io.ObjectInputStream;
32importjava.io.ObjectOutputStream;
33
34/**
35*
36*@authorwhwang
37*2011-12-1下午09:
54:
36
38*/
39publicclassTest1{
40
41publicstaticvoidmain(String[]args)throwsException{
42//writeobject
43StringfileName="obj";
44toWrite(fileName);
45
46//readobject
47toRead(fileName);
48}
49
50publicstaticvoidtoWrite(StringfileName)throwsException{
51ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream(
52fileName));
53oos.writeObject(newA());
54oos.close();
55}
56
57publicstaticvoidtoRead(StringfileName)throwsException{
58ObjectInputStreamois=newObjectInputStream(
59newFileInputStream("obj"));
60At=(A)ois.readObject();
61t.print();
62ois.close();
63}
64
65}
1、直接运行Test1的main方法,运行正确;
2、先将Test1的main方法中的toRead(fileName)注释,把类A中的serialVersionUID值改为1,运行Test1;然后在代开toRead(fileName),将toWrite(fileName)注释,同时将类A中的serialVersionUID值改为2;运行Test1,发现抛出异常,表明如果serialVersionUID不同,即使两个“完全”相同的类也无法反序列化。
[java]viewplaincopyprint?
66Exceptioninthread"main"java.io.InvalidClassException:
wen.hui.test.serializable.A;localclassincompatible:
streamclassdescserialVersionUID=1,localclassserialVersionUID=2
序列化ID在Eclipse下提供了两种生成策略,一个是固定的1L,一个是随机生成一个不重复的long类型数据(实际上是使用JDK工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的1L就可以,这样可以确保代码一致时反序列化成功。
那么随机生成的序列化ID有什么作用呢,有些时候,通过改变序列化ID可以用来限制某些用户的使用。
如Facade模式中,Client端通过FaçadeObject才可以与业务逻辑对象进行交互。
而客户端的FaçadeObject不能直接由Client生成,而是需要Server端生成,然后序列化后通过网络将二进制对象数据传给Client,Client负责反序列化得到Façade对象。
该模式可以使得Client端程序的使用需要服务器端的许可,同时Client端和服务器端的FaçadeObject类需要保持一致。
当服务器端想要进行版本更新时,只要将服务器端的FaçadeObject类的序列化ID再次生成,当Client端反序列化FaçadeObject就会失败,也就是强制Client端从服务器端获取最新程序。
【静态变量序列】
直接看代码:
[java]viewplaincopyprint?
67packagewen.hui.test.serializable;
68
69importjava.io.Serializable;
70
71/**
72*serializable测试
73*
74*@authorwhwang
75*2011-12-1下午09:
50:
07
76*/
77publicclassAimplementsSerializable{
78
79privatestaticfinallongserialVersionUID=1L;
80
81publicstaticintstaticVar=10;
82
83publicA(){
84
85}
86
87publicvoidprint(){
88System.err.println("testserializable");
89}
90
91publicstaticvoidmain(String[]args)throwsException{
92
93}
94}
[java]viewplaincopyprint?
95packagewen.hui.test.serializable;
96
97importjava.io.FileInputStream;
98importjava.io.FileNotFoundException;
99importjava.io.FileOutputStream;
100importjava.io.IOException;
101importjava.io.ObjectInputStream;
102importjava.io.ObjectOutputStream;
103
104/**
105*序列化保存的是对象的状态,静态变量属于类的状态,不会被序列化
106*@authorwhwang
107*2011-12-1下午10:
12:
06
108*/
109publicclassTest2{
110
111publicstaticvoidmain(String[]args){
112try{
113//初始时staticVar为10
114ObjectOutputStreamout=newObjectOutputStream(
115newFileOutputStream("obj"));
116out.writeObject(newA());
117out.close();
118
119//序列化后修改为100
120A.staticVar=100;
121
122ObjectInputStreamoin=newObjectInputStream(newFileInputStream(
123"obj"));
124At=(A)oin.readObject();
125oin.close();
126
127//再读取,通过t.staticVar打印新的值
128System.err.println(t.staticVar);
129
130}catch(FileNotFoundExceptione){
131e.printStackTrace();
132}catch(IOExceptione){
133e.printStackTrace();
134}catch(ClassNotFoundExceptione){
135e.printStackTrace();
136}
137}
138
139}
A类的静态字段staticVar初始化值为10,在Teste2的main方法中,将A类的一个实例系列化到硬盘,然后修改静态字段staticVar=100,接着反系列化刚系列化的对象,输出”该对象的“staticVar的值。
输出的是100还是10呢?
结果输出是100,之所以打印100的原因在于序列化时,并不保存静态变量,这其实比较容易理解,序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量。
【父类的序列化与Transient关键字】
情境:
一个子类实现了Serializable接口,它的父类都没有实现Serializable接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。
解决:
要想将父类对象也序列化,就需要让父类也实现Serializable接口。
如果父类不实现的话的,就需要有默认的无参的构造函数。
在父类没有实现Serializable接口时,虚拟机是不会序列化父对象的,而一个Java对象的构造必须先有父对象,才有子对象,反序列化也不例外。
所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。
因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。
如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如int型的默认是0,string型的默认是null。
Transient关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值被设为初始值,如int型的是0,对象型的是null。
[java]viewplaincopyprint?
140packagewen.hui.test.serializable;
141
142/**
143*
144*@authorwhwang
145*2011-12-1下午10:
23:
10
146*/
147publicclassB{
148publicintb1;
149
150publicintb2;
151
152publicB(){
153this.b2=100;
154}
155}
[java]viewplaincopyprint?
156packagewen.hui.test.serializable;
157
158importjava.io.Serializable;
159
160/**
161*
162*@authorwhwang
163*2011-12-1下午09:
49:
51
164*/
165publicclassCextendsBimplementsSerializable{
166
167privatestaticfinallongserialVersionUID=1L;
168
169publicintc1;
170
171publicintc2;
172
173publicC(){
174//给b1,b2赋值
175this.b1=1;
176this.b2=2;
177this.c2=-100;
178}
179
180}
[java]viewplaincopyprint?
181packagewen.hui.test.serializable;
182
183importjava.io.FileInputStream;
184importjava.io.FileNotFoundException;
185importjava.io.FileOutputStream;
186importjava.io.IOException;
187importjava.io.ObjectInputStream;
188importjava.io.ObjectOutputStream;
189
190/**
191*如果父类没有实现Serializable,那么父类不会被系列化,当反系列化子类时
192*会调用父类无参的构造方法。
193*@authorwhwang
194*2011-12-1下午10:
23:
51
195*/
196publicclassTest3{
197
198publicstaticvoidmain(String[]args){
199try{
200ObjectOutputStreamout=newObjectOutputStream(
201newFileOutputStream("obj"));
202out.writeObject(newC());
203out.close();
204
205ObjectInputStreamoin=newObjectInputStream(newFileInputStream(
206"obj"));
207Ct=(C)oin.readObject();
208oin.close();
209
210System.err.println(t.b1+","+t.b2+","+t.c1+","+t.c2);
211
212}catch(FileNotFoundExceptione){
213e.printStackTrace();
214}catch(IOExceptione){
215e.printStackTrace();
216}catch(ClassNotFoundExceptione){
217e.printStackTrace();
218}
219}
220
221}
运行Test3的main方法,结果输出0,100,0,-100;即在子类的构造方法中对父类的成员变量的初始化没有被系列化;而反系列化时,则是调用父类的无参构造方法实例化父类。
【对敏感字段加密】
情境:
服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
解决:
在序列化过程中,虚拟机会试图调用对象类里的writeObject(ObjectOutputStreadout)和readObject(ObjectInputStreadin)方法(通过反射机制),进行用户自定义的序列化和反序列化,如果没有这样的方法,则默认调用是ObjectOutputStream的defaultWriteObject方法以及ObjectInputStream的defaultReadObject方法。
用户自定义的writeObject和readObject方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。
基于这个原理,可以在实际应用中得到使用,用于敏感字段的加密工作,如:
[java]viewplaincopyprint?
222packagewen.hui.test.serializable;
223
224importjava.io.IOException;
225importjava.io.ObjectInputStream;
226importjava.io.ObjectInputStream.GetField;
227importjava.io.Serializable;
228
229/**
230*@authorwhwang2011-12-1下午10:
29:
54
231*/
232publicclassDimplementsSerializable{
233
234privatestaticfinallongserialVersionUID=1L;
235
236privateStringpassword;
237
238publicD(){
239
240}
241
242publicStringgetPassword(){
243returnpassword;
244}
245
246publicvoidsetPassword(Stringpassword){
247this.password=password;
248}
249
250//privatevoidwriteObject(ObjectOutputStreamout){
251//
252//}
253
254privatevoidreadObject(ObjectInputStreamin){
255try{
256GetFieldreadFields=in.readFields();
257Objectobject=readFields.get("password","");
258System.err.println("要解密的字符串:
"+object.toString());
259password="password";//模拟解密,需要获得本地的密钥
260}catch(IOExceptione){
261e.printStackTrace();
262}catch(ClassNotFoundExceptione){
263e.printStackTrace();
264}
265
266}
267
268}
[java]viewplaincopyprint?
269packagewen.hui.test.serializable;
270
271importjava.io.FileInputStream;
272importjava.io.FileNotFoundException;
273importjava.io.FileOutputStream;
274importjava.io.IOException;
275importjava.io.ObjectInputStream;
276importjava.io.ObjectOutputStream;
277
278/**
279*@ClassName:
Test4
280*@Description:
加密测试。
281*@authorwhwang
282*@date2011-12-1下午05:
01:
34
283*
284*/
285publicclassTest4{
286
287publicstaticvoidmain(String[]args){
288try{
289ObjectOutputStreamout=newObjectOutputStream(
290newFileOutputStream("obj"));
291Dt1=newD();
292t1.setPassword("encryption");//加密后的(模拟)
293out.writeObject(t1);
294out.close();
295
296ObjectInputStreamoin=newObjectInputStream(newFileInputStream(
297"obj"));
298Dt=(D)oin.readObject();
299oin.close();
300
301System.err.println("解密后的字符串:
"+t.getPassword());
302
303}catch(FileNotFoundExceptione){
304e.printStackTrace();
305}catch(IOExceptione){
306e.printStackTrace();
307}catch(ClassNotFoundExceptione){
308e.printStackTrace();
309}
310}
311
312}
在系列化之前,将密码字段加密,然后系列化到硬盘,在反系列化时,通过类D中的readObject(ObjectInputStreamin)做解密操作,确保了数据的安全。
如:
RMI技术是完全基于Java序列化技术的,服务器端接口调用所需要的参数对象来至于客户端,它们通过网络相互传输。
这就涉及RMI的安全传输的问题。
一些敏感的字段,如用户名密码(用户登录时需要对密码进行传输),我
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java Serializable系列化与反系列化 Serializable 系列化