【设计模式】破解单例模式:反射、序列化与克隆攻击的防御之道 ...

打印 上一主题 下一主题

主题 899|帖子 899|积分 2697

大概有的小伙伴看了我上一篇文章里几种方式对比的表格,觉得枚举有缺点,为什么Joshua Bloch还推荐使用枚举?
这就要提到单例的破解了。平凡的单例模式是可以通过反射和序列化/反序列化来破解的,而Enum由于自身的特性问题,是无法破解的。固然,由于这种情况根本不会出现,因此我们在使用单例模式的时间也比较少考虑这个问题。
枚举类是实现单例模式最好的方式

在单例模式的实现中,撤除枚举方法实现的单例模式,其它的实现都可以利用反射构造新的对象,从而粉碎单例模式,但是枚举就不行,下面说说原因:
粉碎单例的方式有 3 种,反射、克隆以及序列化,下面详细介绍:
反射

常见的单例模式实现中,每每有一个私有的构造函数,防止外部步伐的调用,但是通过反射可以轻而易举的粉碎这个限制:
  1. public class DobleCheckSingleton {
  2.     private DobleCheckSingleton(){}
  3.     private static volatile DobleCheckSingleton dobleCheckSingleton;
  4.     public static DobleCheckSingleton getSingleton(){
  5.         if (dobleCheckSingleton == null){
  6.             synchronized (DobleCheckSingleton.class){
  7.                 if (dobleCheckSingleton == null){
  8.                     dobleCheckSingleton = new DobleCheckSingleton();
  9.                 }
  10.             }
  11.         }
  12.         return dobleCheckSingleton;
  13.     }
  14.     public static void main(String[] args) {
  15.         try {
  16.             DobleCheckSingleton dobleCheckSingleton = DobleCheckSingleton.getSingleton();
  17.             Constructor<DobleCheckSingleton> constructor = DobleCheckSingleton.class.getDeclaredConstructor();
  18.             constructor.setAccessible(true);
  19.             DobleCheckSingleton reflectInstance = constructor.newInstance();
  20.             System.out.println(dobleCheckSingleton == reflectInstance);
  21.         } catch (Exception e  e.printStackTrace();
  22.         }
  23.     }
  24. }
  25. 输出:false,单例被破坏
复制代码
显然,通过反射可以粉碎所有含有无参构造器的单例类,如可以粉碎懒汉式、饿汉式、静态内部类的单例模式
但是反射无法粉碎通过枚举实现的单例模式,利用反射构造新的对象,由于 enum 没有无参构造器,效果会抛出 NoSuchMethodException 异常
  1. public enum EnumSingleton {
  2.     INSTANCE;
  3.     public static void main(String[] args) {
  4.         try {
  5.             EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
  6.             // 获取无参的构造函数
  7.             Constructor<EnumSingleton> constructor = null;
  8.             constructor = EnumSingleton.class.getDeclaredConstructor();
  9.             // 使用构造函数创建对象
  10.             constructor.setAccessible(true);
  11.             EnumSingleton reflectInstance = constructor.newInstance();
  12.             System.out.println(enumSingleton == reflectInstance);
  13.         } catch (Exception e) {
  14.             e.printStackTrace();
  15.         }
  16.     }
  17. }
  18. 输出:java.lang.NoSuchMethodException: singleton.EnumSingleton.<init>()
  19.     at java.lang.Class.getConstructor0(Class.java:3082)
  20.     at java.lang.Class.getDeclaredConstructor(Class.java:2178)
  21.     at singleton.EnumSingleton.main(EnumSingleton.java:19)
复制代码
枚举安全的原因表明:

对 EnumSingleton 文件进行反编译,可以发现 EnumSingleton 继续于 Enum,而 Enum 类确实没有无参的构造器,所以抛出 NoSuchMethodException。
  1. 枚举类 EnumSingleton 反编译结果
  2. public final class singleton.EnumSingleton extends java.lang.Enum<singleton.EnumSingleton>
  3. Enum 类的构造方法
  4. protected Enum(String name, int ordinal) {
  5.         this.name = name;
  6.         this.ordinal = ordinal;
  7. }
复制代码
进一步,通过调用父类有参构造器构造枚举实例对象,样例步伐又抛出 IllegalArgumentException 异常。
  1. public enum EnumSingleton {
  2.     INSTANCE;
  3.     public EnumSingleton getInstance(){
  4.         return INSTANCE;
  5.     }
  6.     public static void main(String[] args) {
  7.         try {
  8.             EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
  9.             // 获取无参的构造函数
  10.             Constructor<EnumSingleton> constructor = null;
  11.             constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);
  12.             // 使用构造函数创建对象
  13.             constructor.setAccessible(true);
  14.             EnumSingleton reflectInstance = constructor.newInstance("test",1);
  15.             System.out.println(enumSingleton == reflectInstance);
  16.         } catch (Exception e) {
  17.             e.printStackTrace();
  18.         }
  19.     }
  20. }
  21. 输出:
  22. java.lang.IllegalArgumentException: Cannot reflectively create enum objects
  23.     at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
  24.     at singleton.EnumSingleton.main(EnumSingleton.java:31)
复制代码
因为 Constructor 的 newInstance 方法限定了 clazz 的范例不能是 enum,否则抛出异常
  1. @CallerSensitive
  2. public T newInstance(Object ... initargs)
  3.      throws InstantiationException, IllegalAccessException,
  4.             IllegalArgumentException, InvocationTargetException
  5. {
  6.     ...
  7.      if ((clazz.getModifiers() & Modifier.ENUM) != 0)
  8.          throw new IllegalArgumentException("Cannot reflectively create enum objects");
  9.      ...
  10. }
复制代码
所以枚举类不能通过反射构建构造函数的方式构建新的实例
序列化

先看看通过序列化粉碎单例的例子,此中 Singleton 实现了 Serializable 接口,才有大概通过序列化粉碎单例。
  1. public class DobleCheckSingleton implements Serializable {
  2.     private DobleCheckSingleton() {
  3.     }
  4.     private static volatile DobleCheckSingleton dobleCheckSingleton;
  5.     public static DobleCheckSingleton getSingleton() {
  6.         if (dobleCheckSingleton == null) {
  7.             synchronized (DobleCheckSingleton.class) {
  8.                 if (dobleCheckSingleton == null) {
  9.                     dobleCheckSingleton = new DobleCheckSingleton();
  10.                 }
  11.             }
  12.         }
  13.         return dobleCheckSingleton;
  14.     }
  15.     public static void main(String[] args) {
  16.         try {
  17.             DobleCheckSingleton dobleCheckSingleton = DobleCheckSingleton.getSingleton();
  18.             ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerSingleton.obj"));
  19.             oos.writeObject(dobleCheckSingleton);
  20.             oos.flush();
  21.             oos.close();
  22.             FileInputStream fis = new FileInputStream("SerSingleton.obj");
  23.             ObjectInputStream ois = new ObjectInputStream(fis);
  24.             DobleCheckSingleton s1 = (DobleCheckSingleton) ois.readObject();
  25.             ois.close();
  26.             System.out.println(dobleCheckSingleton == s1);
  27.                         } catch (Exception e) {
  28.             e.printStackTrace();
  29.         }
  30.     }
  31. }
  32. 输出:false,单例被破坏
复制代码
枚举类实现,枚举类不实现 Serializable 接口,都可以进行序列化,并且返回原来的单例。
  1. public enum EnumSingleton {
  2.     INSTANCE;
  3.     public static void main(String[] args) {
  4.         try {
  5.             EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
  6.             ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerSingleton.obj"));
  7.             oos.writeObject(enumSingleton);
  8.             oos.flush();
  9.             oos.close();
  10.             FileInputStream fis = new FileInputStream("SerSingleton.obj");
  11.             ObjectInputStream ois = new ObjectInputStream(fis);
  12.             EnumSingleton s1 = (EnumSingleton) ois.readObject();
  13.             ois.close();
  14.             System.out.println(enumSingleton == s1);
  15.         } catch (Exception e) {
  16.             e.printStackTrace();
  17.         }
  18.     }
  19. }
  20. 输出:true
复制代码
原因: 枚举类的 writeObject 方法仅仅是将 Enum.name 写到文件中,反序列化时,根据 readObject 方法的源码定位到 Enum 的 valueOf 方法,他会根据名称返回原来的对象。
克隆

实现 Cloneable 接口重写 clone 方法,但是 Enum 类中 clone 的方法是 final 范例,无法重写,也就不能通过克隆粉碎单例。
  1. public abstract class Enum<E extends Enum<E>>
  2.         implements Comparable<E>, Serializable {
  3.      protected final Object clone() throws CloneNotSupportedException {
  4.         throw new CloneNotSupportedException();
  5.     }
  6. }  
复制代码
不消枚举如何防止单例模式粉碎

若实现了序列化接口,重写 readResolve 方法即可,反序列化时将调用该方法返回对象实例
  1. public Object readResolve() throws ObjectStreamException {
  2.     return dobleCheckSingleton;
  3. }
复制代码
通过反射粉碎单例的场景,可以在构造方法中判断实例是否已经创建,若已创建则抛出异常
  1. private Singleton(){
  2.     if (instance !=null){
  3.         throw new RuntimeException("实例已经存在,请通过 getInstance()方法获取");
  4.     }
  5. }
复制代码
通过clone粉碎单例的场景,可以重写clone方法,返回已有单例对象
往期推荐


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

张春

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表