反序列化的时候,并不是调用类的构造函数来实现实例的构建,而是通过一种语言之外的对象创建机制来构造对象实例。。从底层源码来看,生成实例时调用了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)