[java设计模式]破解单例设计模式,禁止偷生、超生!
引言
虽然国家规定了计划生育,但是仍然有很多人。冒着坐牢、推房子、丢工作的风险,偷偷的生下自己的宝宝!对程序而言,同样也可以,虽然我们私有化的构造方法,但是同样有很多种方式去创建新的对象。
反射破解法
主要原理就是,我们获取单例类的构造方法,然后把私有访问权限打开。然后进行创建对象。具体代码如下:
首先搞一个单例类:
/**
* 先搞一个单例类。
* @ClassName : Eager //饿汉单例
* @Description : //类加载的时候,进行初始化。
*/
public class Hungry {
private static Hungry instance = new Hungry();
/**
* 私有构造方法
*/
private Hungry() {
}
/**
* 外部过去实例的通道
* @return
*/
public static Hungry getInstance() {
return instance;
}
}
进行破解:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @ClassName : 破解
* @Description :此类演示破解的过程
*/
public class Crack {
public static void main(String[] args) {
// 先去校验我们的单例写的是是否成功,我们没有重写equals方法,所有比较是对象的地址值。
// 此处我们获取了两次,然后进行比较。
if (Hungry.getInstance()==Hungry.getInstance()){
// 输出此句话,说明我们的单例设计模式是成功的。
System.out.println("单例设计模式是成功的");
}
// 正常获取单例对象
Hungry instance = Hungry.getInstance();
try {
// 反射获取构造方法
Constructor<Hungry> declaredConstructor = Hungry.class.getDeclaredConstructor();
// 破解private访问权限
declaredConstructor.setAccessible(true);
// 使用构造方法创建对象
Hungry hungry = declaredConstructor.newInstance();
// 将我们反射获取的对象跟正常渠道获取的单例进行对比
System.out.println(instance==hungry);
// 如果输出的是false,说明就是破解成功了。
} catch (NoSuchMethodException e) {
System.out.println("获取构造方法异常");
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
既然我们反射可以破解我们的单例类,那我们应该知道如何加强防护。
/**
* @ClassName : Eager //饿汉单例
* @Description : //类加载的时候,进行初始化。
*/
public class Hungry {
private static Hungry instance = new Hungry();
/**
* 私有构造方法
*/
private Hungry() {
// 在此处加强防护,针对instance进行判空处理。
if (instance != null) {
throw new IllegalArgumentException("对象不允许反射创建");
}
}
/**
* 外部过去实例的通道
* @return
*/
public static Hungry getInstance() {
return instance;
}
}
利用对象序列化技术破解
还是老样子,首先搞一个单例对象,此实例对象要实现序列化接口
/**
* @ClassName : Eager //饿汉单例
* @Description : //类加载的时候,进行初始化。
*/
public class Hungry {
private static Hungry instance = new Hungry();
/**
* 私有构造方法
*/
private Hungry() {
// 在此处加强防护,针对instance进行判空处理。
if (instance != null) {
throw new IllegalArgumentException("对象不允许反射创建");
}
}
/**
* 外部过去实例的通道
* @return
*/
public static Hungry getInstance() {
return instance;
}
}
使用序列化技术进行破解~
import java.io.*;
/**
* @ClassName : SerializableCrack 序列化破解demo
* @Description : 演示序列化破解单例设计模式
*/
public class SerializableCrack {
public static void main(String[] args) {
// 同样,我们先验证我们的单例是成功的。
if (Hungry.getInstance() == Hungry.getInstance()) {
System.out.println("单例设计模式是成功的!");
}
// 首先,我们获取一个对象
Hungry hungry = Hungry.getInstance();
// 对对象进行序列化的文本中
try {
// 创建一个对象序列化流。我们将对象输出到1.txt文本中,等后续读取使用
ObjectOutputStream objectOutput = new ObjectOutputStream(new FileOutputStream(new File("1.txt")));
// 输出对象
objectOutput.writeObject(hungry);
// 创建一个对象序列化读取流。
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("1.txt")));
// 读取对象
Hungry object = (Hungry)objectInputStream.readObject();
// 进行比较,如果输出的是false,说明破解成功。
System.out.println(hungry==object);
} catch (Exception e) {
System.out.println("序列化失败");
}
}
}
加强防护,可以使用readResolve方法,替换到反序列化的对象。
/**
* @ClassName : Eager //饿汉单例
* @Description : //类加载的时候,进行初始化。
*/
public class Hungry implements Serializable {
private static Hungry instance = new Hungry();
/**
* 私有构造方法
*/
private Hungry() {
// 在此处加强防护,针对instance进行判空处理。
if (instance != null) {
throw new IllegalArgumentException("对象不允许反射创建");
}
}
/**
* 外部过去实例的通道
* @return
*/
public static Hungry getInstance() {
return instance;
}
/**
* 此方法可以替换到反序列化的对象。
*/
Object readResolve() throws ObjectStreamException {
return instance;
}
}
差不多就这个样子了。下一篇讲工厂设计模式~
本作品采用《CC 协议》,转载必须注明作者和本文链接