一、懒汉式单例
package edu.aeon.model.singleton; /** * 单例模式:懒汉式(类装载及创建) * 步骤: * 1.构造器私有化(防止外部new) * 2.提供静态的、私有的代表该对象的实例的空引用 * 3.提供静态的、公开的函数(方法)供外部访问 * @author aeon */ public class LazySingleton { //1.构造器私有化(防止外部new) private LazySingleton(){} //2.提供静态的、私有的代表该对象的实例的空引用(调用时才给值) private static LazySingleton lazySingletonInstance=null; //3.提供静态的、公开的函数(方法)供外部访问应用该模式唯一实例 public static LazySingleton getLazySingletonInstance(){ if(null==lazySingletonInstance){ lazySingletonInstance=new LazySingleton(); } return lazySingletonInstance; } }
二、破解单例模式(除枚举)
2.1通过反射机制来破解上面提供的懒汉式单例
package edu.aeon.model.singleton.crack; import java.lang.reflect.Constructor; import edu.aeon.model.singleton.LazySingleton; /** * [说明]:通过反射机制破解单例模式(除枚举) * 步骤: * 1.加载单例类 * 2.通过反射获得无参构造器 * 3.解除本类访问单例类私有属性的限制(跳过权限的检查)。 * 4.通过反射获得的无参构造器来获得实例 * 5.对比获得实例是否为同一对象 * @author aeon * */ public class CrackSingletonByReflex { public static void main(String[] args) { System.out.println("破解前:"+(LazySingleton.getLazySingletonInstance()==LazySingleton.getLazySingletonInstance()?"是同一对象!":"不是同一对象!")); try { //1.加载单例类 Class<LazySingleton> clazz = (Class<LazySingleton>) Class.forName("edu.aeon.model.singleton.LazySingleton"); //2.通过反射获得无参构造器 Constructor<LazySingleton> constructor= clazz.getDeclaredConstructor(); //3.解除本类访问单例类私有属性的限制(跳过权限的检查)。 constructor.setAccessible(true); //4.通过反射获得的无参构造器来获得实例 LazySingleton lazySingleton1=(LazySingleton) constructor.newInstance(); LazySingleton lazySingleton2=(LazySingleton) constructor.newInstance(); //5.对比获得实例是否为同一对象 System.out.println("破解后:"+(lazySingleton1==lazySingleton2?"是同一对象!":"不是同一对象!")); } catch (Exception e) { e.printStackTrace(); } } }
运行结果截图如下:
很明显的看到通过反射机制可以破坏这种单例模式的本质。那么如何防止呢?
2.1.1防止反射机制破解单例模式
package edu.aeon.model.singleton; /** * 单例模式:懒汉式(类装载及创建) * 步骤: * 1.构造器私有化(防止外部new) * 2.提供静态的、私有的代表该对象的实例的空引用 * 3.提供静态的、公开的函数(方法)供外部访问 * @author aeon */ public class LazySingleton { //1.构造器私有化(防止外部new) private LazySingleton(){ if(null!=lazySingletonInstance){ throw new RuntimeException("单例模式不允许外界使用构造器!"); } } //2.提供静态的、私有的代表该对象的实例的空引用(调用时才给值) private static LazySingleton lazySingletonInstance=null; //3.提供静态的、公开的函数(方法)供外部访问应用该模式唯一实例 public static LazySingleton getLazySingletonInstance(){ if(null==lazySingletonInstance){ lazySingletonInstance=new LazySingleton(); } return lazySingletonInstance; } }
我们再次运行通过反射机制破解单例模式程序、可以发现:
原理:当存在这个单例的唯一实例的时候,通过反射机制跳过权限检查再次使用构造器构造对象的时候,通过抛出运行时异常的形式来阻止外界使用构造器来构造实例。
2.2通过序列化/反序列化破解单例模式
package edu.aeon.model.singleton.crack; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import edu.aeon.model.singleton.LazySingleton; /** * [说明]:通过序列化/反序列化破解单例模式(除枚举) * 步骤: * 1.序列化单例类对象实例 * 2.通过刚才生成的序列化对象文件反序列化生成对象 * @author aeon * */ public class CrackSingletonBySerialize { public static void main(String[] args) { LazySingleton lazySingleton1 = LazySingleton.getLazySingletonInstance(); LazySingleton lazySingleton2 = LazySingleton.getLazySingletonInstance(); System.out.println((lazySingleton1 == lazySingleton2 ? "破解前是同一对象!" : "破解前不是同一对象!")); // 1.序列化单例类对象实例 FileOutputStream fileOutputStream = null; ObjectOutputStream objectOutputStream = null; FileInputStream fileInputStream = null; ObjectInputStream objectInputStream = null; try { fileOutputStream = new FileOutputStream("b:/lazySingletonInstance.txt"); objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(lazySingleton1); System.out.println("序列化对象成功!"); } catch (IOException e) { e.printStackTrace(); } finally { try { objectOutputStream.close(); } catch (IOException e1) { e1.printStackTrace(); } try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } // 2.通过刚才生成的序列化对象文件反序列化生成对象 try { fileInputStream = new FileInputStream("b:/lazySingletonInstance.txt"); objectInputStream = new ObjectInputStream(fileInputStream); LazySingleton lazySingleton3 = (LazySingleton) objectInputStream.readObject(); System.out.println((lazySingleton1 == lazySingleton3 ? "破解后是同一对象!" : "破解后不是同一对象!")); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { objectInputStream.close(); } catch (IOException e) { e.printStackTrace(); } try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
运行结果截图如下:
2.2.1如何防止序列化/反序列化破解单例模式
package edu.aeon.model.singleton; import java.io.Serializable; /** * 单例模式:懒汉式(类装载及创建) * 步骤: * 1.构造器私有化(防止外部new) * 2.提供静态的、私有的代表该对象的实例的空引用 * 3.提供静态的、公开的函数(方法)供外部访问 * @author aeon */ public class LazySingleton implements Serializable { //1.构造器私有化(防止外部new) private LazySingleton(){ if(null!=lazySingletonInstance){ throw new RuntimeException("单例模式不允许外界使用构造器!"); } } //2.提供静态的、私有的代表该对象的实例的空引用(调用时才给值) private static LazySingleton lazySingletonInstance=null; //3.提供静态的、公开的函数(方法)供外部访问应用该模式唯一实例 public static LazySingleton getLazySingletonInstance(){ if(null==lazySingletonInstance){ lazySingletonInstance=new LazySingleton(); } return lazySingletonInstance; } /** * 反序列化时通过该默认方法返回对象 * @return */ private Object readResolve(){ return lazySingletonInstance; } }
我们再运行2.2代码、运行结果截图:
原理:如果反序列化时定义了readResolve()方法、则会通过该方法返回对象的实例,而不是再序列化一个新对象返回、所以能保持返回的是同一对象的实例。
这样就解决了单例模式中反射机制、序列化/反序列化漏洞。