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