北京尚学堂笔记--利用反射和反序列化去获取单例对象

前言:

单例模式并不是完全安全的(枚举式除外),因为在Java中可以通过反射和反序列化来破解单例模式的实现方式,具体原理如下所示(单例模式以懒汉式为例):

1、利用反射破解单例:

代码演示:

public class ClientTest {
	public static void main(String[] args) throws Exception{
		SingletonDemo s1 = SingletonDemo.getInstance();
		SingletonDemo s2 = SingletonDemo.getInstance();
		
		System.out.println(s1);
		System.out.println(s2);
		
		
		 //通过反射的方式直接调用私有构造器
		 Class<SingletonDemo> clazz = (Class<SingletonDemo>)Class.forName("singleton.SingletonDemo");
		 Constructor<SingletonDemo> constructor = clazz.getDeclaredConstructor(null);
		 constructor.setAccessible(true);
		 SingletonDemo s3 = constructor.newInstance();
		 SingletonDemo s4 = constructor.newInstance();
		  
		 System.out.println(s3);
		 System.out.println(s4);
	}
}

s1和s2是通过单例对象调用方法获得单例实例,输出结果一致。而s3和s4是通过反射获得的对象,它们的输出结果与s1、s2不同。

输出结果如下:

singleton.SingletonDemo02R@6d06d69c
singleton.SingletonDemo02R@6d06d69c
singleton.SingletonDemo02R@7852e922
singleton.SingletonDemo02R@4e25154f

如何防止他人利用反射来获取单例对象呢?

可以在构造方法中手动抛出异常控制,原理就是在第二次创建单例的时候判断单例是否为null,如果不为null的情况,手动抛出一个RuntimeException()。

代码演示:

public class SingletonDemo{
	//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
	private static SingletonDemo instance;
	
	//初始化构造器
	private SingletonDemo(){
		if(instance!=null){
			throw new RuntimeException("实例已存在,不能再建!");
		}
	}
	
	//方法同步,调用效率低
	public static synchronized SingletonDemo getInstance(){
		if(instance == null){
			instance = new SingletonDemo();
		}
		return instance;
	}
}

当在单例模式中手动跑出异常控制后,输出结果会出现异常,具体如下所示:

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.lang.reflect.Constructor.newInstance(Unknown Source)
    at singleton.ClientTest.main(ClientTest.java:22)
Caused by: java.lang.RuntimeException: 实例已存在,不能再建!
    at singleton.SingletonDemo02R.<init>(SingletonDemo02R.java:12)
    ... 5 more
singleton.SingletonDemo02R@6d06d69c
singleton.SingletonDemo02R@6d06d69c

2、利用反序列化破解单例:

在利用反序列化进行破解单例的时候,首先需要在原单例模式上实现接口Serializable,因为如果不实现接口的话,会出现 java.io.NotSerializableException的异常。具体代码如下:

public class SingletonDemo implements Serializable{//实现Serializable接口
	//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
	private static SingletonDemo instance;
	
	//初始化构造器
	private SingletonDemo(){
		if(instance!=null){
			throw new RuntimeException("实例已存在,不能再建!");
		}
	}
	
	//方法同步,调用效率低
	public static synchronized SingletonDemo getInstance(){
		if(instance == null){
			instance = new SingletonDemo();
		}
		return instance;
	}
}
public class ClientTest {
	public static void main(String[] args) throws Exception{
		SingletonDemo s1 = SingletonDemo.getInstance();
		SingletonDemo s2 = SingletonDemo.getInstance();
		
		System.out.println(s1);
		System.out.println(s2);

		//通过反序列化的方式构造多个对象
		FileOutputStream fos = new FileOutputStream("d:/a.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(s1);
		oos.close();
		fos.close();
		
		FileInputStream fis = new FileInputStream("d:/a.txt");
		ObjectInputStream ois = new ObjectInputStream(fis);
		SingletonDemo s3 = (SingletonDemo)ois.readObject();
		ois.close();
		fis.close();
		
		System.out.println(s3);
	}
}

输出结果如下:

singleton.SingletonDemo02R@6d06d69c
singleton.SingletonDemo02R@6d06d69c
singleton.SingletonDemo02R@119d7047

如何防止他人利用反射来获取单例对象呢?

反序列化时,会在原单例实现模式中调用readResolve()这个方法,这时,直接返回此方法指定的对象,而不需要单独再创建新对象。

代码演示:

public class SingletonDemo02R implements Serializable{
	//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
	private static SingletonDemo02R instance;
	
	//初始化构造器
	private SingletonDemo02R(){
		if(instance!=null){
			throw new RuntimeException("实例已存在,不能再建!");
		}
	}
	
	//方法同步,调用效率低
	public static synchronized SingletonDemo02R getInstance(){
		if(instance == null){
			instance = new SingletonDemo02R();
		}
		return instance;
	}
	
	//反序列化时,会直接调用readResolve()这个方法
	private Object readResolve() throws Exception{
		return instance;
	}
}

这时输出结果如下:

singleton.SingletonDemo02R@6d06d69c
singleton.SingletonDemo02R@6d06d69c
singleton.SingletonDemo02R@6d06d69c

3、总结

反射可以破解上一条博客提到的几种(不包含枚举式)实现方式!

可以在构造方法中手动抛出异常控制

反序列化也可以破解上一条博客提到的几种(不包含枚举式)实现方式!

可以通过定义readResolve()防止获得不同对象。反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象。

4、针对上一条博客测试效率代码CountDownLatch类(闭锁)的补充说明

CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。它有两个常用的方法:

countDown():当前线程调用此方法,则计数减一(减一要放在finally里面执行)。

await():调用此方法会一直阻塞当前线程,直到计时器的值为0。

猜你喜欢

转载自blog.csdn.net/qq_41204714/article/details/81413445
今日推荐