[今日考题]-JAVA序列化方面

今日考题 JAVA序列化方面

题目共三题:

  1. Serializable 与 Externalizable的区别;
  2. 说明 Serializable 接口 WriteReplace、ReadResolve 机制及用途
  3. 编程题, 写一个单例模式可序列化类,并保证其不能被反射重复创建

上述问题涉及到java序列化(Serializability)过程的相关知识,以下逐层以源码方式进行相关说明。以下源码以 java 8 进行描述。

问题1

首先要说明 什么叫序列化(Serializability)及序列化的用途。简单的来说序列化(Serializability)就是将一个对象的状态(各个属性量)保存起来,然后在适当的时候再获得。对应的序列化过程就会有 序列化(serialization)反序列化(deserialization) 两个过程。

序列化(serialization)过程则是将对象Object 通过ObjectOutputStream 进行IO流输出,反序列化(deserialization)则是通过 ObjectInputStream 将对应的IO流 转化为 Object对象。通过序列化操作,就可以把对应的对象以流的方式保存为文件,推送给其他外部系统等等,所以序列化可以认为是一个对象可以支持分布式计算的标志。

Serializable 是一个 可序列化标识接口,用于表示某类的实例化对象可以被序列化。它和Cloneable一样是标记型接口(tagging interface),是一个空接口,但对应的有若干特殊签名方法(special methods with exact signatures),包括以下内容

//负责写入特定类的对象的状态
private void writeObject(java.io.ObjectOutputStream out) throws IOException
//负责从流中读取并恢复类字段
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
//非正常情况下进行恢复类字段
private void readObjectNoData()  throws ObjectStreamException;
//将对象写入流前需要指定要使用的替代对象的可序列化类
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
//从流中读取类的一个实例,进行返回时需要指定替代的类
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

上述的前三个方法是用于控制对象进行特殊化序列化/反序列化操作。而对应的writeReplace 则是序列化写入前进行相应的替换,如将对象的一个加密字段替换为一个固定标识;对应的readResolve 则是反序列化返回对应的对象前来进行相应解决处理(含意不同于solve),如将对象的某一个字段进行修改或返回指定单例模式对象等。

此外Serializable 类 需要设置静态 (static)、最终 (final) 、 long类型的 serializableVersionUID字段,用于标识本类的版本,确认相应的兼容性。若未设置,则会在序列化过程中重新计算一个对应的serialVersionUID,且由于不同版本的编译器(compiler)差异问题,反而会导致出现 InvalidClassException 异常,所以强烈建议指定或生成一个对应的serialVersionUID。

  	ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

Externalizable 则继承了 Serializable 接口,并附加writeExternal,readExternal 方法用来实现对对象序列化/反序列化的完全控制。即要存储的每个对象都需要检测是否支持 Externalizable 接口。如果对象支持 Externalizable,则调用 writeExternal 方法。如果对象不支持 Externalizable 但实现了 Serializable,则使用 ObjectOutputStream 保存该对象。在重构 Externalizable 对象时,先使用无参数的公共构造方法创建一个实例,然后调用 readExternal 方法。通过从 ObjectInputStream 中读取 Serializable 对象可以恢复这些对象。

以上内容可以通过对应的 ObjectOutputStream /ObjectInputStream 来看出相关端倪,如下:

//ObjectOutputStream 部分代码
private void writeOrdinaryObject(Object obj,
                                     ObjectStreamClass desc,
                                     boolean unshared)
        throws IOException
    {
			//此处省略部分代码....
			if (desc.isExternalizable() && !desc.isProxy()) {
                writeExternalData((Externalizable) obj);
            } else {
                writeSerialData(obj, desc);
            }
//此处省略部分代码....

//ObjectInputStream 部分代码
private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
//此处省略部分代码....
		if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }
//此处省略部分代码....

问题 2

关于问题2 其实在 问题1 已经给出了相应的答案,其实质还是在于对序列化/反序列化进行过程的相关控制。实际序列化操作都会使用到对应的ObjectStreamClass 类,在该类中构造器则会通过反射的方式来判断是否可序列化、是否有对应的Method等,参考如下代码

	private ObjectStreamClass(final Class<?> cl) {
        this.cl = cl;
        name = cl.getName();
        isProxy = Proxy.isProxyClass(cl);
        isEnum = Enum.class.isAssignableFrom(cl);
        serializable = Serializable.class.isAssignableFrom(cl);
        externalizable = Externalizable.class.isAssignableFrom(cl);
		//此处省略部分代码....
		if (serializable) {
		//此处省略部分代码....
			if (externalizable) {
                cons = getExternalizableConstructor(cl);
                } else {
                cons = getSerializableConstructor(cl);
                writeObjectMethod = getPrivateMethod(cl, "writeObject",
                	new Class<?>[] { ObjectOutputStream.class },
                            Void.TYPE);
                readObjectMethod = getPrivateMethod(cl, "readObject",
                    new Class<?>[] { ObjectInputStream.class },
                            Void.TYPE);
                readObjectNoDataMethod = getPrivateMethod(
                    cl, "readObjectNoData", null, Void.TYPE);
                    hasWriteObjectData = (writeObjectMethod != null);
                }
                domains = getProtectionDomains(cons, cl);
                writeReplaceMethod = getInheritableMethod(
                    cl, "writeReplace", null, Object.class);
                readResolveMethod = getInheritableMethod(
                    cl, "readResolve", null, Object.class);
//此处省略部分代码....

而在对应的ObjectInputStream 类 readOrdinaryObject方法就可以获知到实际运行反序列化过程中,同样以反射的方式运行了readResolve方法。

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
    	//此处省略部分代码....
    	ObjectStreamClass desc = readClassDesc(false);
	    //此处省略部分代码....
	    if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;

同理 ObjectOutputStream 也有对应 invokeWriteReplace 的操作,不过相应的源码解析要相对复杂一点,因为会涉及到类的逐层继承问题,需要迭代地进行相关判断及处理。

	private void writeObject0(Object obj, boolean unshared)
        	throws IOException
    	{
	    	//此处省略部分代码....
			for (;;) {
                // REMIND: skip this check for strings/arrays?
                Class<?> repCl;
                desc = ObjectStreamClass.lookup(cl, true);
                if (!desc.hasWriteReplaceMethod() ||
                    (obj = desc.invokeWriteReplace(obj)) == null ||
                    (repCl = obj.getClass()) == cl)
                {
                    break;
                }
                cl = repCl;
            }
	    	//此处省略部分代码....

问题3

有了前面 问题2 的基础就可以利用readResolve来解决,这个比较容易写,以下以静态内部类的方式来说明及进行相关验证。

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

/**
 * 可序列化的单例类
 * @author zhangb
 *
 */
public class MySingletonDemo implements Serializable {

	private static final long serialVersionUID = 1L;

	/**
	 * 静态内部类
	 * 
	 * @author zhangb
	 *
	 */
	private static class SingletonClassInstance {
		private static final MySingletonDemo instance = new MySingletonDemo();
	}

	private MySingletonDemo() {
		//以下禁止反射
		if(SingletonClassInstance.instance!=null) {
			throw new RuntimeException();
		}
	}

	public static MySingletonDemo getInstance() {
		return SingletonClassInstance.instance;
	}

	// 利用 readResolve来强制返回当前的实例
	private Object readResolve() {
		return getInstance();
	}

	public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

		MySingletonDemo demo = MySingletonDemo.getInstance();
		System.out.println(demo);
		
		// 以下测试 反序列化
		Path demoFile = FileSystems.getDefault().getPath(System.getProperty("java.io.tmpdir"),"demo.demo");
		System.out.println(demoFile.toString());
		
		ObjectOutputStream objectOutputStream = new ObjectOutputStream(
				Files.newOutputStream(demoFile, StandardOpenOption.CREATE));
		objectOutputStream.writeObject(demo);
		
		if(demoFile.toFile().canRead()) {
			System.out.println("size:"+demoFile.toFile().length());
		}
		
		ObjectInputStream objectInputStream = new ObjectInputStream(
				Files.newInputStream(demoFile));
		MySingletonDemo demo1 = (MySingletonDemo) objectInputStream.readObject();
		System.out.println(demo1.toString());
		System.out.println(demo1 == demo);
		

		//以下测试反射
		Class<MySingletonDemo> clazz = MySingletonDemo.class;
		Constructor<MySingletonDemo> constructor = clazz.getDeclaredConstructor(null);
		constructor.setAccessible(true);
		MySingletonDemo demo2 = constructor.newInstance(null);
		System.out.println(demo2);
		System.out.println(demo2 == demo);
		
	}

}

测试时可以注释对应的readResolve方法来进行测试。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值