序列化

来源:互联网 发布:抢月饼javascript 编辑:程序博客网 时间:2024/05/17 05:04

1、基类不可序列化,子类实现序列化——示例
2、自定义序列化——示例
3、版本进化,域:少--->多
4、版本进化,域:多--->少
5、保护性地编写 readObject 方法
6、对于实例控制,枚举类型优于 readResolve
7、用序列化代理代替序列化实例

--------------------------------------------------------------------------------------

1、基类不可序列化,子类实现序列化——示例

基类:

public abstract class AbstractFoo {private int x, y;//This enum and field are used to track initializationprivate enum State{ NEW, INITIALIZING, INITIALIZED };private final AtomicReference<State> init = new AtomicReference<State>( State.NEW );public AbstractFoo(int x, int y){initialize(x, y);}//This constructor and the following method allow//subclass's readObject method to initialize our stateprotected AbstractFoo(){}protected void initialize(int x, int y) {if( !init.compareAndSet(State.NEW, State.INITIALIZING) ){throw new IllegalStateException("Already initialized");}this.x = x;this.y = y;//Do anything else the original constructor didinit.set(State.INITIALIZED);}//These methods provide access to internal state so it can//be manually serialized by subclass's writeObject method.protected final int getX(){checkInit();return x;}protected final int getY(){checkInit();return y;}//Must call from all public and protected instance methodsprivate void checkInit(){if( init.get() != State.INITIALIZED ){throw new IllegalStateException("Uninitialized");}}}

子类:

public class Foo extends AbstractFoo implements Serializable {private static final long serialVersionUID = -7017537330505867253L;//子类新增域private String name;public Foo( int x, int y, String name ){super( x, y );this.name = name;}public String getName() {return name;}private void readObject( ObjectInputStream s ) throws IOException, ClassNotFoundException{s.defaultReadObject();//Manually deserialize and initialize superclass stateint x = s.readInt();int y = s.readInt();initialize(x, y);}private void writeObject( ObjectOutputStream s ) throws IOException{s.defaultWriteObject();//Manually serialize superclass states.writeInt( getX() );s.writeInt( getY() );}}

测试代码,测试通过!

@Testpublic void testSerialize() throws IOException{//Write object of Foo to fileFoo f = new Foo(11 , 22, "YY");ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("serialize.object"));out.writeObject( f );out.close();//Read object of Foo from fileObjectInputStream in = new ObjectInputStream( new FileInputStream("serialize.object"));Foo f1 = null;try {f1 = (Foo)in.readObject();} catch (ClassNotFoundException e) {e.printStackTrace();}in.close();//Test f eqeals f1assertEquals(f.getX(), f1.getX());assertEquals(f.getY(), f1.getY());assertEquals(f.getName(), f1.getName());}

2、自定义序列化——示例

约束:大多数实例域,或者所有的实例域都应该被标记为 transient 。

缺陷:域不能声明为 final。

public final class StringList implements Serializable {private transient int size = 0;private transient Entry head = null;//No longer Serializableprivate static class Entry{String data;Entry next;Entry previous;}//Appends the specified string to the listpublic final void add(String s){//Add first Entryif( head == null ){head = new Entry();head.data = s;head.previous = null;head.next = null;size++;return;}//Add Entry to tailEntry tail = null;//Find tail nodefor( tail = head ; tail.next != null ; tail = tail.next );//New Entry nodeEntry newEntry = new Entry();newEntry.data = s;//Add Entry nodetail.next = newEntry;newEntry.previous = tail;size++;return ;}//For test add methodpublic void printStringList(){System.out.println( "Size=" + size);if(head == null) return ;for( Entry e = head; e != null; e = e.next){System.out.println( e.data );}}private void writeObject( ObjectOutputStream out ) throws IOException{out.defaultWriteObject();out.writeInt( size );for( Entry e = head; e != null; e = e.next ){out.writeObject(e.data);//注意:是String 对象,不是Entry 对象}}private void readObject( ObjectInputStream in ) throws IOException, ClassNotFoundException{in.defaultReadObject();int numElements = in.readInt();//Read in all elements and insert them in listfor( int i = 0; i < numElements; i++ ){add( (String)in.readObject());}}}

无论你是否使用默认的序列化形式,如果在读取整个对象状态的任何其他方法上强制任何同步,则也必须在对象序列化上强制这种同步!

private synchronized void  writeObject( ObjectOutputStream out ) throws IOException{out.defaultWriteObject();}

3、版本进化,域:少--->多

版本一:

public class FewWriteMoreRead implements Serializable {private static final long serialVersionUID = 7211818804850424395L;private final String name;private final int age;public FewWriteMoreRead(String name, int age) {super();this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}}

将版本一的对象,序列化后写入“fewWriteMoreRead.object”文件

public void testWriteFewToFile() throws IOException{FewWriteMoreRead fwmr = new FewWriteMoreRead("GongQiang", 24);ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("fewWriteMoreRead.object") );oos.writeObject(fwmr);oos.close();}

版本二:

public class FewWriteMoreRead implements Serializable {private static final long serialVersionUID = 7211818804850424395L;private final String name;private final int age;private String sex;public FewWriteMoreRead(String name, int age) {super();this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}public void setSex(String sex) {this.sex = sex;}public String getSex() {return sex;}}

从文件“fewWriteMoreRead.object”读取,还原为版本二的对象——测试通过

public void testReadMoreFromFile() throws IOException, ClassNotFoundException{ObjectInputStream ois = new ObjectInputStream( new FileInputStream("fewWriteMoreRead.object") );FewWriteMoreRead fwmr = (FewWriteMoreRead) ois.readObject();assertEquals("GongQiang", fwmr.getName());assertEquals(24, fwmr.getAge());System.out.println( fwmr.getSex() );//outputs: null}

如果在版本二中,注释掉 serialVersionUID ,则会转换异常!

java.io.InvalidClassException: demo.serializable.FewWriteMoreRead; local class incompatible: stream classdesc serialVersionUID = 7211818804850424395, local class serialVersionUID = -7774025334847164354

4、版本进化,域:多--->少

直接将上面 3 的过程倒过来,并且 serialVersionUID 不重新计算,能否正常转换?

答案:能!

可见 serialVersionUID 的值是多少并不重要,重要的是要保持不变!

将 3 的版本二的对象序列化后写入“moreWriteFewRead.object”文件

public void testWriteMoreToFile() throws IOException{FewWriteMoreRead fwmr = new FewWriteMoreRead("GongQiang", 24);fwmr.setSex("man");ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("moreWriteFewRead.object") );oos.writeObject(fwmr);oos.close();}

从文件“moreWriteFewRead.object”读取,还原为 3 的版本一的对象——测试通过

public void testReadFewFromFile() throws IOException, ClassNotFoundException{ObjectInputStream ois = new ObjectInputStream( new FileInputStream("moreWriteFewRead.object") );FewWriteMoreRead fwmr = (FewWriteMoreRead) ois.readObject();assertEquals("GongQiang", fwmr.getName());assertEquals(24, fwmr.getAge());}

5、保护性地编写 readObject 方法

如果某些情况下 readObjec 方法不做特殊处理,会被攻击!具体攻击方法见书本

public final class Period2 implements Serializable{private static final long serialVersionUID = -9042067415108167318L;/** * 本来应该声明为 * private final Date start; * private final Date end; * 但是由于自定义序列化,不支持 final 域,因而只能改写如下: */private  Date start;private  Date end;/** * @param start The beginning of the period * @param end The end of the period; must not precede start * @throws IllegalArgumentException If start is after end * @throws NullPointerException If start or end is null */public Period2(Date start, Date end) {super();this.start = new Date(start.getTime());this.end = new Date(end.getTime());if( this.start.compareTo(this.end)>0 ){throw new IllegalArgumentException( start + " after " + end );}}public Date getStart() {return new Date(start.getTime());}public Date getEnd() {return new Date(end.getTime());}@Overridepublic String toString() {return "{ " + start + "-" + end + " }";}//构造函数的特殊处理,这里也必须实现private void readObject( ObjectInputStream in ) throws IOException, ClassNotFoundException{in.defaultReadObject();//Defensively copy our mutable componentsstart = new Date( ((Date)in.readObject()).getTime() );end = new Date( ((Date)in.readObject()).getTime() );//Check that our invariants are satisfiedif( start.compareTo(end) > 0 ){throw new InvalidObjectException( start + " after " + end );}}private void writeObject( ObjectOutputStream out ) throws IOException{out.defaultWriteObject();out.writeObject( start );out.writeObject( end );}}

还有一点要注意:自定义序列化后,readObject / writeObject 方法必须成对出现!

假设上例不实现 writeObject 方法,会抛出如下异常:

java.io.OptionalDataException

6、对于实例控制,枚举类型优于 readResolve

如果依赖 readResolve 进行实例控制,带有对象引用类型的所有实例域都必须声明为 transient 。否则可以被攻击!

public class Elvis implements Serializable {private static final long serialVersionUID = -4647550481369280022L;//Here use transientprivate static final transient Elvis INSTANCE = new Elvis();private Elvis(){}public static Elvis getInstance(){return INSTANCE;}//Here use transientprivate transient String[] favoriteSongs = {"Hound Dog", "Heartbreak Hotel"};public void printFavorites(){System.out.println( Arrays.toString(favoriteSongs) );}private Object readResolve(){return INSTANCE;}}

当然,更好的方法使用枚举:

public enum Elvis2 {INSTANCE;private String[] favoriteSongs = {"Hound Dog", "Heartbreak Hotel"};public void printFavorites(){System.out.println( Arrays.toString(favoriteSongs) );}}

7、用序列化代理代替序列化实例

步骤:

1、为可序列化的类设计一个私有的静态嵌套类,精确地表示外围类的实例的逻辑状态。

2、将如下 writeReplace 方法添加到外围类中。通过序列化代理,这个方法可以被逐字地复制到任何类中。

3、为确保攻击无法得逞,在外围类中添加如下 readObject 方法。

4、在 SerializationProxy 类中提供一个 readResolve 方法,它返回一个逻辑上相当的外围类的实例。

public final class Period implements Serializable{private final Date start;private final Date end;/** * @param start The beginning of the period * @param end The end of the period; must not precede start * @throws IllegalArgumentException If start is after end * @throws NullPointerException If start or end is null */public Period(Date start, Date end) {super();this.start = new Date(start.getTime());this.end = new Date(end.getTime());if( this.start.compareTo(this.end)>0 ){throw new IllegalArgumentException( start + " after " + end );}}public Date getStart() {return new Date(start.getTime());}public Date getEnd() {return new Date(end.getTime());}@Overridepublic String toString() {return "{ " + start + "-" + end + " }";}//writeReplace method for the serialization proxy patternprivate Object writeReplace(){return new SerializationProxy( this );}//readObject method for the serialization proxy patternprivate void readObject(ObjectInputStream in) throws InvalidObjectException{throw new InvalidObjectException("Proxy required.");}//Serialization proxy for Period classprivate static class SerializationProxy implements Serializable{private static final long serialVersionUID = 2365956597772614139L;private final Date start;private final Date end;public SerializationProxy( Period p) {this.start = p.start;this.end = p.end;}//readResolve method for Period.SerializationProxyprivate Object readResolve(){return new Period( start, end );}}}

测试代码——测试成功:

public class PeriodTest {@Testpublic void testPeriod() throws IOException, ClassNotFoundException {Period p = new Period(new Date(0),new Date());//Write objectByteOutputStream bos = new ByteOutputStream();ObjectOutputStream oos = new ObjectOutputStream( bos );oos.writeObject(p);oos.close();//Read objectObjectInputStream ois = new ObjectInputStream( bos.newInputStream() );Period p1 = (Period) ois.readObject();ois.close();assertEquals(p.getStart(), p1.getStart());assertEquals(p.getEnd(), p1.getEnd());}}

局限:

1、它不能与可以被客户端扩展的类兼容。

2、它也不能与对象图中包含循环的某些类兼容。