破环单例模式及解决方案
一、如何破环单例模式
破环单例模式很简单,使单例类创建多个对象即可,枚举方式除外
二、破坏单例模式的方式
常见的破环单例模式的方式就是序列化反序列化和反射
示例在:
1.序列化反序列化
以静态内部类创建单例模式为例
public class DestroySingleton { public static void main(String[] args) throws IOException, ClassNotFoundException { //writeObjectFile(); readObjectFile(); //两次获取到的不是同一个对象,说明单例创建了多个对象,单例模式破坏成功 readObjectFile(); } //向文件中写数据(对象) public static void readObjectFile() throws IOException, ClassNotFoundException { //创建对象输入流对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\Users\apple\Desktop\a.txt")); //读取对象 LazyMan4 instance = (LazyMan4) ois.readObject(); System.out.println(instance); //释放资源 ois.close(); } //从文件中读数据(对象) public static void writeObjectFile() throws IOException { //获取单例对象 LazyMan4 instance = LazyMan4.getInstance(); //创建对象输出流对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\Users\apple\Desktop\a.txt")); //写对象 oos.writeObject(instance); //释放资源 oos.close(); } }
writeObjectFile()方法会将对象创建出一个.txt文件,在调用readObjectFile()方法读取文件对象时,可以看到两个对象不相同,说明单例模式已被破坏
2.反射
以静态内部类创建的单例为例
public class DestroySingleton2 { public static void main(String[] args) throws Exception { //获取单例类的字节码对象 Class clazz = LazyMan4.class; //获取无参构造方法对象 Constructor cons =clazz.getDeclaredConstructor(); //暴力反射 cons.setAccessible(true); //创建对象 LazyMan4 l1 =(LazyMan4) cons.newInstance(); LazyMan4 l2 =(LazyMan4) cons.newInstance(); //false 两个对象地址不同,说明单例创建了对各对象,单例模式破坏成功 System.out.println(l1 == l2); } }
三、解决方案
1.序列化反序列化
在单例类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象
public class LazyMan implements Serializable { private LazyMan(){} //定义一个静态内部类 private static class LazyManHolder{ private static final LazyMan INSYANCE = new LazyMan(); } //对外访问方法 public static LazyMan getInstance(){ return LazyMan.LazyManHolder.INSYANCE; } //防止被破坏 //当进行反序列化时,会自动调用该方法,将该方法的返回值直接反回 public Object readResolve(){ return LazyManHolder.INSYANCE; } }
2.反射
在构造方法中加同步块
class LazyMan3{ private static boolean flag = false; private LazyMan3() { synchronized (LazyMan3.class) { //判断flag的值是否为true,如果是true,说明非第一次访问,直接抛出一个异常 //如果为false,说明是第一次访问 if (flag) { throw new RuntimeException("不能创建多个对象"); }//将flag设置为true flag = true; } } //定义一个静态内部类 private static class LazyMan3Holder{ private static final LazyMan3 INSYANCE = new LazyMan3(); } //对外访问方法 public static LazyMan3 getInstance(){ return LazyMan3.LazyMan3Holder.INSYANCE; } }