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 initialization private 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 state protected 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 did init.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 methods private 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 state int x = s.readInt(); int y = s.readInt(); initialize(x, y); } private void writeObject( ObjectOutputStream s ) throws IOException{ s.defaultWriteObject(); //Manually serialize superclass state s.writeInt( getX() ); s.writeInt( getY() ); } }
测试代码,测试通过!
@Test public void testSerialize() throws IOException{ //Write object of Foo to file Foo f = new Foo(11 , 22, "YY"); ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("serialize.object")); out.writeObject( f ); out.close(); //Read object of Foo from file ObjectInputStream 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 f1 assertEquals(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 Serializable private static class Entry{ String data; Entry next; Entry previous; } //Appends the specified string to the list public final void add(String s){ //Add first Entry if( head == null ){ head = new Entry(); head.data = s; head.previous = null; head.next = null; size++; return; } //Add Entry to tail Entry tail = null; //Find tail node for( tail = head ; tail.next != null ; tail = tail.next ); //New Entry node Entry newEntry = new Entry(); newEntry.data = s; //Add Entry node tail.next = newEntry; newEntry.previous = tail; size++; return ; } //For test add method public 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 list for( 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()); } @Override public String toString() { return "{ " + start + "-" + end + " }"; } //构造函数的特殊处理,这里也必须实现 private void readObject( ObjectInputStream in ) throws IOException, ClassNotFoundException{ in.defaultReadObject(); //Defensively copy our mutable components start = new Date( ((Date)in.readObject()).getTime() ); end = new Date( ((Date)in.readObject()).getTime() ); //Check that our invariants are satisfied if( 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 transient private static final transient Elvis INSTANCE = new Elvis(); private Elvis(){} public static Elvis getInstance(){ return INSTANCE; } //Here use transient private 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()); } @Override public String toString() { return "{ " + start + "-" + end + " }"; } //writeReplace method for the serialization proxy pattern private Object writeReplace(){ return new SerializationProxy( this ); } //readObject method for the serialization proxy pattern private void readObject(ObjectInputStream in) throws InvalidObjectException{ throw new InvalidObjectException("Proxy required."); } //Serialization proxy for Period class private 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.SerializationProxy private Object readResolve(){ return new Period( start, end ); } } }
测试代码——测试成功:
public class PeriodTest { @Test public void testPeriod() throws IOException, ClassNotFoundException { Period p = new Period(new Date(0),new Date()); //Write object ByteOutputStream bos = new ByteOutputStream(); ObjectOutputStream oos = new ObjectOutputStream( bos ); oos.writeObject(p); oos.close(); //Read object ObjectInputStream ois = new ObjectInputStream( bos.newInputStream() ); Period p1 = (Period) ois.readObject(); ois.close(); assertEquals(p.getStart(), p1.getStart()); assertEquals(p.getEnd(), p1.getEnd()); } }
局限:
1、它不能与可以被客户端扩展的类兼容。
2、它也不能与对象图中包含循环的某些类兼容。