记录:反射与反序列化影响下的单例模式

单例模式在反射和反序列化的情况下会被破坏,创造出不止一个实例。

反射下的单例模式:

public class TestSingleClass1 {

	private TestSingleClass1() {
	
	}
	
	public static class SingleInnerClass{
		private static final TestSingleClass1 testSingleClass1 = new TestSingleClass1();
	}
	
	public static TestSingleClass1 getInstance() {
		return SingleInnerClass.testSingleClass1;
	}
	
	public void test() {
		System.out.println("testtest......");
	}
	
	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		Class clazz = Class.forName("com.wmx.single.TestSingleClass1");
		TestSingleClass1 tsc1 = (TestSingleClass1) clazz.newInstance();
		TestSingleClass1 tsc2 = (TestSingleClass1) clazz.newInstance();
		TestSingleClass1 tsc3 =TestSingleClass1.getInstance();
		TestSingleClass1 tsc4 =TestSingleClass1.getInstance();
		System.out.println(tsc1.hashCode());
		System.out.println(tsc2.hashCode());
		System.out.println(tsc3.hashCode());
		System.out.println(tsc4.hashCode());
		tsc1.test();
		tsc2.test();
		tsc3.test();
	}
}

在这段代码下,用反射的方式创建了两个实例,然后用单例模式获得两次实例,然后打印它们的hashcode,运行结果如下:
在这里插入图片描述
后面两个hashcode打印出来是一样的,单例模式没有问题,前面的却都不一样,说明用反射生成的实例是全新的实例,这样就破坏了单例模式。

解决方法:增加标志记录,只有第一次构造方法被调用时才能执行,其他都会抛出异常:

public class TestSingleClass1 {
	
	private static boolean flag = false;
	//第一次调用后修改flag,注意要加同步锁,否则依然可能出来两个实例
	private TestSingleClass1() {
		synchronized (TestSingleClass1.class) {
			if(flag == false) {
				flag = true;
			}else {
				throw new RuntimeException("not singleton!");
			}
		}
	}
	
	public static class SingleInnerClass{
		private static final TestSingleClass1 testSingleClass1 = new TestSingleClass1();
	}
	
	public static TestSingleClass1 getInstance() {
		return SingleInnerClass.testSingleClass1;
	}
	
	public void test() {
		System.out.println("testtest......");
	}
	
	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		Class clazz = Class.forName("com.wmx.single.TestSingleClass1");
		TestSingleClass1 tsc6666 = TestSingleClass1.getInstance();
		TestSingleClass1 tsc1 = (TestSingleClass1) clazz.newInstance();
	}
}

运行结果:
在这里插入图片描述
第二次调用构造方法的时候就报异常了,维持住了单例。
这里有一个问题就是需要保证获取单例先于反射执行,否则先反射的话,获取单例的getInstance方法就废掉了,效果就是调用方法就报异常。

反序列化影响单例:

public class TestSingleClass2 implements Serializable{

	private static final long serialVersionUID = 7198203598944411394L;
	
	private TestSingleClass2() {
		
	}
	
	public static class SingleInnerClass{
		private static final TestSingleClass2 testSingleClass2 = new TestSingleClass2();
	}
	
	public static TestSingleClass2 getInstance() {
		return SingleInnerClass.testSingleClass2;
	}
	
	public void test() {
		System.out.println("testtest......");
	}
	

	public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
		//序列化对象
		TestSingleClass2 tsc = TestSingleClass2.getInstance();
		ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("test.txt")));
		oo.writeObject(tsc);
		oo.close();
		
		//反序列化对象
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("test.txt")));
		TestSingleClass2 tsc1 = (TestSingleClass2) ois.readObject();
		
		System.out.println(tsc.hashCode());
		System.out.println(tsc1.hashCode());
	}

}

一个实现了序列化接口的单例类,在main方法里先序列化再反序列化获得对象,打印两个实例的hashcode:
在这里插入图片描述
hashcode不同,单例又被破坏了
解决方法:在单例类里添加一个readResolve方法:

/**
	 * 防止序列化反序列化破坏单例模式的方法
	 * @return
	 */
	private Object readResolve() {
		return SingleInnerClass.testSingleClass2;
	}

添加这个方法之后,再执行main方法,这样反序列化之后也是同样的对象了:
在这里插入图片描述
在网上看到一条评论比较形象:浅拷贝与深拷贝的区别

为什么加了这个readResolve之后就返回相同hashcode的对象了呢,我去翻了翻jdk源码,由于水平渣渣,只能猜个大概,我也不知道对不对。
我认为事情出在反序列化读取对象的readObject方法上:

//反序列化对象
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("test.txt")));
		TestSingleClass2 tsc1 = (TestSingleClass2) ois.readObject();

这个readObject()方法大概是这样的:

public final Object readObject()
        throws IOException, ClassNotFoundException
    {
        //前面看不懂,略掉
        try {
            Object obj = readObject0(false);
            //这块儿也看不懂,略掉
            return obj;
        } finally {
        	//看不懂,略掉
        }
    }

然后顺藤摸瓜,去看readObject0这个方法(老外起名字也这么随便么。。。):

//我比较懒,只放这个方法里我猜测的关键部分
case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));

继续,去看readOrdinaryObject方法:

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
       //看不懂,略掉

        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            //看不懂,略掉
        }

        return obj;
    }

这里有个条件判断desc.hasReadResolveMethod(),猜测是反序列化的类里有没有readResolve方法的一个判断,然后进来if块来调用invokeReadResolve方法:

Object invokeReadResolve(Object obj)
        throws IOException, UnsupportedOperationException
    {
        //看不懂,略掉
        if (readResolveMethod != null) {
            try {
                return readResolveMethod.invoke(obj, (Object[]) null);
            } //后面一大堆异常处理,看不懂,不贴了
        } else {
            throw new UnsupportedOperationException();
        }
    }

readResolveMethod.invoke(obj, (Object[]) null);我猜测关键应该在这里,直接调了method.invoke方法,应该就是去调用了在单例类里实现的readResolve方法,那个方法直接返回现成的单例对象,所以维持了单例模式。
然后我想验证一下,于是打了几个断点进了debug,基本就是这个流程,invoke之后就进了单例类的readResolve方法,效果和调用getInstance是一样的,返回相同的对象,维持住了单例模式。
不知道我的猜测是不是对的,只是猜测,仅供参考。
最后,网上说,最好还是推荐用枚举类的方法创造单例类,不用搞这些乱七八糟的,就正常创建,什么反射,反序列化都不会破坏掉单例模式,反正就看起来很高端。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值