序列化

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、它也不能与对象图中包含循环的某些类兼容。

你可能感兴趣的:(序列化)