`
liyebing
  • 浏览: 58080 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

序列化揭秘(二)

阅读更多

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

    asp.net揭秘(第二版)

    书中将解释HTTP协议,Web API的控制器和路由,以及数据序列化等。 5. **ASP.NET Core**:第二版还可能涵盖了ASP.NET Core,这是跨平台的下一代ASP.NET框架。ASP.NET Core集成了Web Forms、MVC和Web API,具有高性能...

    实战 Web Service 压缩传输

    #### 二、揭秘 DataSet 序列化的内幕 在Web Service中,DataSet作为常用的数据容器,在数据传输过程中扮演着重要角色。然而,原始的DataSet序列化方式存在较大的数据冗余,尤其是在包含大量数据时,这不仅增加了...

    SpringBoot揭秘 快速构建微服务体系_SpringSprintBoot揭秘_

    开发者将学习如何利用Spring MVC,结合@Controller、@RequestMapping等注解定义HTTP端点,以及如何使用Jackson库进行JSON序列化和反序列化。此外,还会介绍Spring Data JPA和Hibernate,用于简化数据库操作,实现ORM...

    MATLAB揭秘.zip

    - 读写文件:MATLAB可以通过fread、fwrite、textscan等函数读取二进制或文本文件,save和load函数用于序列化和反序列化MATLAB变量。 - 数据导入导出:可以将数据导入到MATLAB工作空间,或者导出为其他格式(如CSV...

    揭秘系列-生物学]Biology.Demystified.pdf

    - **线粒体**:细胞的能量工厂,通过氧化磷酸化过程产生能量。 ### 三、分子生物学 #### DNA与遗传 - **DNA结构**:双螺旋结构,由四种碱基组成(腺嘌呤A、鸟嘌呤G、胞嘧啶C、胸腺嘧啶T)。 - **遗传密码**:DNA上...

    Android应用开发揭秘

    对于网络编程,书中会介绍如何使用HTTP协议进行数据交换,以及使用JSON或XML格式进行数据序列化和反序列化。同时,还会讲解如何实现异步任务处理,比如使用AsyncTask或Handler,以避免阻塞UI线程。 进一步,书中的...

    ASP.NET Web API 2 框架揭秘-带源码版--蒋金楠(大内老A)

    7. **媒体类型格式化**:Web API可以自动序列化和反序列化数据,使其能以不同的格式(如JSON或XML)传输。这部分将讨论如何处理不同的数据格式。 8. **测试与调试**:介绍如何测试Web API服务,包括单元测试、集成...

    MATLAB 揭秘 David McMahon 著 郑碧波 译

    1. **统计分析**:MATLAB提供了丰富的统计函数,用于数据的描述性统计、假设检验、回归分析、时间序列分析等。例如,`mean()`、`std()`用于计算平均值和标准差,`histogram()`绘制直方图,`corrcoef()`计算相关系数...

    hessian-4.0.7.jar + src

    在软件开发领域,Hessian是一种高效的二进制序列化协议,它由Caucho Technology公司开发,常用于实现远程方法调用(RMI)和Web服务。本次我们将深入探讨Hessian 4.0.7版本,包括其jar包的使用以及源码分析,帮助...

    交错ADC揭秘.pdf

    当多个通道的转换信号被用于调制并出现在最终的数字化输出中时,就会出现杂散信号。 在交错ADC系统中,即使输入信号是正弦波,也会出现增益杂散。若ADC之间存在增益误差失配,即两个ADC的增益不相等,那么交错的...

    android应用开发揭秘

    - JSON解析:介绍如何使用Gson、Jackson或org.json库解析和序列化JSON数据。 通过以上章节的学习,开发者可以掌握Android应用开发的基础和进阶技巧,包括界面设计、程序流程控制、数据管理和网络通信等关键领域。...

    《Android应用开发揭秘》源码8-16

    开发者将了解如何使用HttpURLConnection或OkHttp库发送网络请求,以及如何处理JSON数据,如使用Gson或Jackson库进行序列化和反序列化。 第14章可能会讲解Android的权限管理和安全相关的话题。随着Android系统的更新...

    spark内核揭秘

    1. 内存管理:通过Tungsten项目,Spark实现了自定义的内存管理,优化了数据序列化和反序列化,减少GC开销。 2. 数据本地性:Spark会尽可能地将任务调度到数据所在的节点执行,提高数据读取速度。 3. 执行计划优化...

    Android应用开发揭秘-书籍所需源码

    这一章可能详细讲解了使用HTTP/HTTPS进行网络请求,如使用Android的HttpURLConnection、OkHttp库或者Retrofit库进行网络请求,还有JSON解析(Gson或Jackson)以及数据的序列化和反序列化。 4. **第14章**:可能涉及...

    《Prototype and script.aculo.us终极揭秘》

    5. **Effect模块**:Effect模块提供了高级动画效果,如Parallel(并行效果)、Sequence(序列效果)和Transition(过渡效果),可以创建复杂的动画序列。 通过学习《Prototype和script.aculo.us终极揭秘》这本书,...

    《逆向工程揭秘》中文版 共3部分.part1

    这几个附录远远超出了简单的汇编语言参考向导,讲述了公共代码段(common code fragments)和常用编译器对几种典型的代码序列表现出来的编译器习性(complier idioms),并介绍了识别和破解它们的方法。

    gwt 揭秘 源码

    通过序列化Java对象,GWT可以自动处理客户端与服务器间的类型转换和通信细节。 6. **源码分析** 书中的"part1,2,3"可能分别涵盖了GWT的基础、进阶和实战内容。通过源码学习,开发者可以了解GWT内部如何处理事件、...

    WCF揭秘基础部分 实例(vs2010)

    - **数据序列化**:选择高效的序列化器,如BinaryFormatter或DataContractSerializer。 - **缓存策略**:利用缓存减少不必要的服务调用。 本实例通过VS2010展示了WCF的基本用法,包括服务创建、调用和服务配置。...

    MATLAB揭秘中文版-扉页.doc

    例如,信号处理工具箱可用于分析和处理时间序列数据,图像处理工具箱则适用于图像分析和处理任务。 为了提升MATLAB的可扩展性,书中的高级章节可能会讨论MATLAB与外部程序(如C、C++或Fortran)的接口,以及如何...

Global site tag (gtag.js) - Google Analytics