设计模式其他常见面试题
参考资料:
Java序列化破坏单例模式的解决方案N种(好文)枚举单例模式如何防止反射攻击
关于“如何破坏单例“我说了好几种方式,面试官却说:我其实随便问问,没想到你真会 装饰设计模式和代理设计模式的区别
建议先阅读
1、为什么枚举单例类可以避免反射攻击
反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。所以枚举类不能通过反射来创建对象
扩展:枚举类如何实现线程安全:
通过反编译后,可以看到单例枚举类是一个final类,且创建该对象的实例是在一个static静态语句块中进行的,根据JVM的类加载机制,静态语句块只会在类被加载时执行一次,所以可以线程安全。另外因为单例枚举类反编译后实际上是一个被final修饰的类,所以他不能被继承,也就不能创建子类对象。 经过反编译后public final class EnumSingleton extends Enum< EnumSingleton> { public static final EnumSingleton ENUMSINGLETON; private static final Singleton ENUM$VALUES[]; public static EnumSingleton[] values(); public static EnumSingleton valueOf(String s); static { ENUM$VALUES = (new EnumSingleton[] { ENUMSINGLETON }); }; }参考:枚举单例模式如何防止反射攻击
2、单例模式存在的问题
1、初始化的线程安全问题 2、反序列化破坏单例 ObjectInputStream使用readObject的方法来实现对象的反序列化,readObject()方法会调用一个名为readOrdinaryObject的方法,该方法会通过反射的方式调用无参构造方法新建一个对象。 反序列化破坏单例public class SerializableDemo { public static void main(String[] args) throws IOException, ClassNotFoundException { // Write to file ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile")); oos.writeObject(Singleton.getSingleton()); // Read from file File file = new File("tempFile"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); Singleton newInstance = (Singleton) ois.readObject(); System.out.println(newInstance == Singleton.getSingleton()); } } //false
解决办法:
1. 在单例类中增加一个 readResolve() 方法,方法中返回单例实例。因为 readOrdinaryObject() 方法在通过反射创建完对象后,会有一个条件判断,如果对象所在的类存在 readResolve() 方法,则会改为使用 readResolve() 方法获取实例对象,而不是利用反射创建出来的对象 参考:Java序列化破坏单例模式的解决方案N种(好文)关于“如何破坏单例“我说了好几种方式,面试官却说:我其实随便问问,没想到你真会
import java.io.Serializable; /** * 使用双重校验锁方式实现单例 */ public class Singleton implements Serializable{ private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } private Object readResolve() { return singleton; } }2. 使用枚举单例模式 反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。所以枚举类不能通过反射来创建对象
public enum EnumSingleton { INSTANCE; public EnumSingleton getInstance(){ return INSTANCE; } }参考: