序列化揭秘(二)

     反序列化的时候,并不是调用类的构造函数来实现实例的构建,而是通过一种语言之外的对象创建机制来构造对象实例。。从底层源码来看,生成实例时调用了java.reflect.Constructor 的newInstance()方法:

 

// 用反射生成实例
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
//。。。。。。此处省略。。。。。。。
return (T) constructorAccessor.newInstance(initargs);
}

 

     而最后实际上是调用了ConstructorAccessor 的newInstance()方法,而再往下,ConstructorAccessor 调用了本地方法。

 

   由此产生了两个问题:

   (一)、通过序列化可以任意创建实例,不受任何限制。那么如何确保单例类反序列化的时候满足单例特性

   (二)、由于不调用类自己的构造器,可能通过构造器来保证的一些限制条件就很难满足。比如,有两个参数max,min,构造 时必须满足max>min

 

 

  下面提供了解决上述两个问题的方法。

  先看看单例模式是如何被序列化蹂躏的,再来看看我们解决方案。

 

// 单例类
public class ASingleton implements Serializable{
private static final long serialVersionUID = -4647546107954516623L;
private static final ASingleton INSTANCE = new ASingleton();
private ASingleton(){
System.out.println("construct a ASingleton");
}
public static ASingleton getInstance(){
return INSTANCE;
}
}


// 单例测试类
public class TestSingleton {
public static void main(String[] args) throws IOException, ClassNotFoundException{
ASingleton s = ASingleton.getInstance();
SerialUtils.writeObject(s, "asingleton.byte");
System.out.println(s);
SerialUtils.readObject("asingleton.byte");
}
}
 

   测试方法会打印出序列化前对象,和反序列化后的对象,我们看看是否是同一个实例

   construct a ASingleton
   file size:51bytes
   test.serial.resolve.ASingleton@3dfeca64
   read form asingleton.byte get test.serial.resolve.ASingleton@457471e0

 

   可以看出来不是同一个实例。这个问题的解决方案是:可以通过覆写readResolve 这个方法来实现,readResolve 会在反序列化时调用,当从流中获取信息重建对象生成实例后,将会调用这个方法,将生成的实例替换成我们想要的实例,看代码:

 

 

// 单例类覆写了readResolve
public class ASingleton implements Serializable{
// 此处省略100 个字
private Object readResolve(){
System.out.println("resolve instance");
return INSTANCE;}
}
 

   可以看出,我们覆写了这个方法,并且将那个唯一的实例(INSTANCE)返回,从而保证了单例。看
看运行结果证明我们的想法:

construct a ASingleton
file size:51bytes
test.serial.resolve.ASingleton@3dfeca64
resolve instance
read form asingleton.byte get test.serial.resolve.ASingleton@3dfeca64

 

可以看出是一个实例,并且readResolve 也被调用了。
到底替换实例的过程是怎样的呢,我们在jdk 源码中寻求答案:

 

// 源码中是如何使用readResolve的
private Object readOrdinaryObject(boolean unshared) throws IOException{
// ...
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
}
// ....
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
Object rep = desc.invokeReadResolve(obj);
// …
if (rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}

    从代码可以看出,原理是这样的:
   (1)首先会从流中读取信息,重建对象obj
   (2) 判断是否覆写了readResolve 方法,如果没有覆写则直接返回obj
   (3) 如果覆写了readResolve 方法,则调用readResolve 方法返回实例rep,如果rep与obj不相等,则用rep 替换obj,最后返回obj替换过程就是这样的,至此,单例问题解决了。

 

 

  最后看看第二个问题如何解决。第二个问题的解决思路有两个,一个是覆写readObject 并且在方法里校验我们的约束信息,如果有问题就抛出异常。代码如下:

// 利用readObject 来防攻击
public class Range implements Serializable{
// 此出省略若干字
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException{
s.defaultReadObject();
int max = this.max;
int min = this.min;
if(max < min){
throw new IllegalArgumentException("max must bigger than min");
}
}
// ….
}

 

   另一个解决思路是让对象实现ObjectInputValidation中的public void validateObject() throws InvalidObjectException 方法。在该方法中校验反序列化之后得实例必须满足的某些特性。如不满足则抛出相应的异常。代码如下:

 

     序列化类的代码如下:

 

package zheyuan.experiment4.com;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectInputValidation;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class ValidationPerson implements Serializable,ObjectInputValidation {
	
	private static final long serialVersionUID = 5497993111607600169L;
	private String firstName;
	private String lastName;
	
	private List<String> lis=new ArrayList<String>();
	
	public ValidationPerson(){
		lis.add("validationPerson");
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public List<String> getLis() {
		return lis;
	}

	public void setLis(List<String> lis) {
		this.lis = lis;
	}

	public void validateObject() throws InvalidObjectException {
		//这里的这个异常是肯定会抛出来的,只是为了演示该方法的使用
		if(this.lis.contains("validationPerson")){
			throw new InvalidObjectException("validationPerson缺失!");
		}
	}
	
	//注册验证对象
	private void readObject(ObjectInputStream in)
	   throws IOException, ClassNotFoundException {
	    in.registerValidation(this, 1);
	    in.defaultReadObject();
	  }
}

 

   测试类的代码如下:

 

package zheyuan.experiment4.com;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;

public class ValidationSerializableTest {

	/**
	 * 反序列化的时候通过继承实现接口ObjectInputValidation中的方法校验反序列化之后类的某些特性
	 * @param args
	 */
	public static void main(String[] args) throws Throwable{

		ValidationPerson vp=new ValidationPerson();
		//序列化
		OutputStream out=new FileOutputStream("validation_sec");
		ObjectOutputStream oos=new ObjectOutputStream(out);
        oos.writeObject(vp);
		
        //反序列化
        InputStream ins=new FileInputStream("validation_sec");
        ObjectInputStream ois=new ObjectInputStream(ins);
        ValidationPerson vpNew=(ValidationPerson)ois.readObject();
        System.out.println(vpNew.getLis().get(0));
	}

}

 

   运行结果如下:

 

 

Exception in thread "main" java.io.InvalidObjectException: validationPerson缺失!
	at zheyuan.experiment4.com.ValidationPerson.validateObject(ValidationPerson.java:50)
	at java.io.ObjectInputStream$ValidationList$1.run(ObjectInputStream.java:2207)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.io.ObjectInputStream$ValidationList.doCallbacks(ObjectInputStream.java:2203)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:358)
	at zheyuan.experiment4.com.ValidationSerializableTest.main(ValidationSerializableTest.java:27)
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值