参考资料:
建议先阅读《设计模式学习》
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; } }
3、InpuStream和outputStream用了什么设计模式,为啥要使用包装设计模式(对象适配器模式),为什么不用代理设计模式
代理模式与装饰者模式看起来很像,都实现基础对象实现的接口,在其自身对象中都保存着对被代理/被装饰者的对象引用。
先来看看装饰者模式的定义:动态的将责任附加到被装饰者对象上,用于扩展对象的功能。比继承的灵活性大。典型的如Java IO的设计即是装饰者模式的典型应用。
代理模式模式的定义:对其他对象进行代理,以控制对被代理对象的访问。Spring的为业务逻辑层方法生成的代理类,主要进行一些事务控制等。
由定义可以看出装饰的责任是扩展功能 ,而代理主要控制访问。比如要将连接进行close()操作如果要放回连接池中而不是关闭这个链接,那么就是对原有功能的扩展增强,应该使用装饰设计模式,而像事务,权限、安全等访问控制的增强则应该使用代理设计模式。可以说一个是增强攻击力,一个是增强防御力。
这里明显是对原有功能进行扩展,而非访问控制,所以使用装饰设计模式更合适。
4、工厂方法模式相比于简单工厂方法有什么优势
简单工厂模式有两大缺点,一是不符合开闭原则,每次增加新类或者修改类都必须修改工厂,二是模块庞大,随着类的增多,创建对象的模块会越来越大,这两点就降低了简单工厂的可维护性。
工厂方法既符合符合开闭原则又不会使工厂模块庞大不堪,因为它增加新类只需增加一个对应的工厂即可。
5、为什么需要工厂,工厂有什么优势,为什么不直接在程序里创建对象
隐藏创建对象的细节,对外提供创建对象的接口,实现模块化编程,提高程序的可读性和可维护性,编码者不性需要知晓创建对象的细节,所以降低了编码难度,减少了出错概率。
在程序中创建对象的不足:
1. 需要开发者知晓创建对象的细节,增大了编码难度,也就增大了出错概率
2. 在所有需要对象的地方都把创建对象的所有步骤都写一遍增加了很多冗余代码
3. 一旦创建对象的某个细节发生变化,所有创建了该对象的程序都必须修改,代码的可维护性低