首页 >> 知识 >> 你知道实现单例的最佳方法是枚举吗?

你知道实现单例的最佳方法是枚举吗?

从jdk1.5开始,可通过编写一个包含单个元素的枚举类型来实现单例:

public enum Singleton { uniqueInstance; public void SingletonOperation() { ...... }}//然后就可以通过Singleton.uniqueInstace.SingletonOperaion来调用

这种方法在功能上与共有域方法相近,但它更加简洁,无偿地提供了序列化的机制,绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击时也能防止。 单元素的枚举类型是实现Singleton的最佳方法。

首先来了解常用的单例模式为何能被攻击破坏。

破坏单例模式

破坏单例模式主要有三种方式:克隆、反射和序列化。简单了解一下这三种方式如何破坏单例。

克隆 这种攻击方式只针对实现了Cloneable接口的单例类。clone方法是不会调用构造函数的,它是直接从内存中copy内存区域的。 而clone方法是Object类的protected方法,默认情况下一个对象是不能直接调用clone方法的。 对于有实现Cloneable接口需求的单例类,应重写clone方法,直接返回其内部的instance,而不能直接返回super.clone()。 public class testSingleton { public static void main(String[] args) throws Exception{ Singleton singleton = Singleton.getInstance(); Singleton clone = (Singleton) singleton.clone(); System.out.println(singleton == clone); } public static class Singleton implements Cloneable{ private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }} 反射 反射可以获取类的构造函数,同时再通过setAccessible(true)就可调用私有构造函数来创建对象。 要防止反射攻击,只能在单例构造方法中检测instance是否为null,或使用first执行标志等,只要是第二次调用了构造方法就抛异常。 public class testSingleton { public static void main(String[] args) throws Exception{ Singleton singleton = Singleton.getInstance(); Constructor constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton reflect = (Singleton)constructor.newInstance(); System.out.println(singleton == reflect); } public static class Singleton{ private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }} 序列化 这种攻击也只对实现了Serializable接口的单例有效,而有些单例恰巧是必须序列化的。 序列化的攻击方式如下所示,主要问题就在inputStream.readObject中。 public class testSingleton { public static void main(String[] args) throws Exception{ Singleton singleton = Singleton.getInstance(); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("serfile")); outputStream.writeObject(Singleton.getInstance()); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("serfile")); Singleton seriable = (Singleton) inputStream.readObject(); System.out.println(singleton == seriable); } public static class Singleton implements Serializable{ private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }}

在ObjectInputStream.readObject方法执行时,其内部方法readOrdinaryObject中执行了该语句:

obj = desc.isInstantiable() ? desc.newInstance() : null;

其中desc是类描述符,也就是说,如果一个实现了Serializable/Externalizable接口的类可以在运行时实例化,那么就调用newInstance()方法,使用其默认构造方法反射创建新的对象实例,自然也就破坏了单例性。 要防御序列化攻击,就得将instance

网站地图