接上篇 单例模式(1)
序列化破坏单例模式
饿汉式的单例类
public class HungarySingleton {
private final static HungarySingleton hungarySingleton = new HungarySingleton();
private HungarySingleton(){
}
public static HungarySingleton getInstance(){
return hungarySingleton;
}
}
序列化破坏单例模式
HungarySingleton hungarySingleton = HungarySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hungarySingleton"));
oos.writeObject(hungarySingleton);
File file = new File("hungarySingleton");
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
HungarySingleton newInstance = (HungarySingleton) objectInputStream.readObject();
System.out.println(hungarySingleton); //HungarySingleton@1e80bfe8
System.out.println(newInstance); //HungarySingleton@4bf558aa
System.out.println((hungarySingleton == newInstance)); //false
通过序列化和反序列化,获取的实例是不同的。这个违背了单例模式的初衷。
这是因为反射对单例进行了破坏。
解决办法
public class HungrySingleton implements Serializable {
private final static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
private Object readResolve(){
return hungrySingleton;
}
}
增加一个readResolve方法
这样就可以在反序列化的时候不会创建新的实例了。
ps:具体为何增加这个方法,可以查看java反序列化的源码
反射造成的破坏
懒汉式和静态内部类,在类加载的时候,实例就会被创建好。
/**
* 使用反射对单例模式进行破坏
*/
@org.junit.Test
public void test02() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 饿汉式:在类加载的时候进行初始化
Class objectClass = HungrySingleton.class;
/**
* getDeclaredConstructor(Class<?>... parameterTypes)
* 这个方法会返回制定参数类型的所有构造器,包括public的和非public的,当然也包括private的。
* getDeclaredConstructors()的返回结果就没有参数类型的过滤了。
*/
Constructor constructor = objectClass.getDeclaredConstructor();
// 默认为false,不能调用private的构造方法,需要设置成true才可以调用
constructor.setAccessible(true);
HungrySingleton instance = HungrySingleton.getInstance();
HungrySingleton newInstance =(HungrySingleton)constructor.newInstance();
System.out.println(instance);//HungrySingleton@e73f9ac
System.out.println(newInstance); // HungrySingleton@61064425
System.out.println(instance == newInstance); // false
}
通过测试,发现:反射的对象和原本的实例化对象不一致。
这是因为反射可以调用私有的构造器创建对象。
如何解决:禁止调用私有构造器
public class HungrySingleton implements Serializable {
private final static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){
if (hungrySingleton != null){
throw new RuntimeException("禁止反射调用,单例构造器");
}
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
private Object readResolve(){
return hungrySingleton;
}
}
Q:饿汉式是否有这种情况
/**
* 使用方式破坏懒汉式单例模式
*/
@org.junit.Test
public void test03() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 获取单例
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
/**
* 通过反射获取
*/
Class<LazySingleton> lazySingletonClass = LazySingleton.class;
Constructor<LazySingleton> constructor = lazySingletonClass.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton newInstance = constructor.newInstance();
System.out.println(newInstance);
System.out.println((instance == newInstance)); // false
}
同理,懒汉式也是存在的。
那么是否可以和饿汉式一样的解决办法呢?、
我们实验一下:
修改懒汉式代码
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton(){
// 构造函数一定为私有的,防止外部new
if (lazySingleton != null){
throw new RuntimeException("禁止反射调用");
}
}
public static LazySingleton getInstance(){
if (lazySingleton == null){
synchronized(LazySingleton.class){
if (lazySingleton != null){
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
}
测试代码
/**
* 使用方式破坏懒汉式单例模式2
*/
@org.junit.Test
public void test04() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1.先获取单例
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
/**
* 2.后通过反射获取
*/
Class<LazySingleton> lazySingletonClass = LazySingleton.class;
Constructor<LazySingleton> constructor = lazySingletonClass.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton newInstance = constructor.newInstance(); // java.lang.RuntimeException: 禁止反射调用
System.out.println(newInstance);
// 结果就不一致了,就无法阻止反射创建对象。
System.out.println((instance == newInstance)); // false
}
测试结果是会抛出异常,但是。。。
由于懒汉式是不是在类加载初期创建实例,而是在调用实例方法的时候才实例对象。所有如果在反射实例化对象之前没有实例化的化,反射就可以获取到了。如下:
/**
* 使用方式破坏懒汉式单例模式
*/
@org.junit.Test
public void test03() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
/**
* 1.先通过反射获取
*/
Class<LazySingleton> lazySingletonClass = LazySingleton.class;
Constructor<LazySingleton> constructor = lazySingletonClass.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton newInstance = constructor.newInstance(); // 没有异常
System.out.println(newInstance);
// 2.后获取单例
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
// 结果就不一致了,就无法阻止反射创建对象。
System.out.println((instance == newInstance)); // false
}
获取到不同的结果。
又想:是否可以不用lazySingleton != null 进行判断,加入一个flag的量?
单例对象类
// 其他代码省略
/**
* 第一次是可以调用构造方法
*/
private static boolean flag = true;
private LazySingleton(){
if (flag){
flag = false;
}else{
throw new RuntimeException("禁止反射调用");
}
// // 构造函数一定为私有的,防止外部new
// if (lazySingleton != null){
// throw new RuntimeException("禁止反射调用");
// }
}
测试方法
/**
* 反射破坏懒汉式。使用flag变量避免
*/
@org.junit.Test
public void test05() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
/**
* 通过反射调取
*/
Class<LazySingleton> lazySingletonClass = LazySingleton.class;
Constructor<LazySingleton> constructor = lazySingletonClass.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton lazySingleton = constructor.newInstance();
System.out.println(lazySingleton);
/**
* 通过实例方法
*/
LazySingleton instance = LazySingleton.getInstance(); // java.lang.RuntimeException: 禁止反射调用
System.out.println(instance);
}
/**
* 反射破坏懒汉式。使用flag变量避免
*/
@org.junit.Test
public void test06() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
/**
* 通过实例方法
*/
LazySingleton instance = LazySingleton.getInstance();
/**
* 通过反射调取
*/
Class<LazySingleton> lazySingletonClass = LazySingleton.class;
Constructor<LazySingleton> constructor = lazySingletonClass.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton lazySingleton = constructor.newInstance(); // java.lang.RuntimeException: 禁止反射调用
System.out.println(lazySingleton);
System.out.println(instance);
}
上面的测试例子可以发现:只能使用一次构造方法
但是反射同样可以对这个flag得值进行修改。
/**
* 反射破坏懒汉式。使用flag变量避免
*/
@org.junit.Test
public void test07() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
/**
* 通过实例方法
*/
LazySingleton instance = LazySingleton.getInstance(); // java.lang.RuntimeException: 禁止反射调用
/**
* 通过反射调取
*/
Class<LazySingleton> lazySingletonClass = LazySingleton.class;
Constructor<LazySingleton> constructor = lazySingletonClass.getDeclaredConstructor();
constructor.setAccessible(true);
// 在调用一次构造方法之后,反射就不能调用了,但反射可以强制修改常亮。
/**
* 强制修改加载过的LazySingleton的常量
*/
Field flag = instance.getClass().getDeclaredField("flag");
flag.setAccessible(true);
flag.set(instance,true);
// 可以正常调用 无报错
LazySingleton lazySingleton = constructor.newInstance();
System.out.println(lazySingleton);
System.out.println(instance);
System.out.println((lazySingleton == instance)); //flase
}
通过上面的案例可以看到,懒汉式不可能完全避免反射对单例模式的破坏。