《Effective Java》-——用私有构造器或者枚举类型强化Singleton属性
Singleton指仅仅被实例化一次的类。Singleton通常被用来代表那些本质上唯一的系统组件,比如窗口管理器或者文件系统。使类成为Singleton会使它的客户端测试变得十分困难,因为无法给Singleton替换模拟实现,除非它实现一个充当其类型的接口。
在Java 1.5发行版本之前,实现Singleton有两种方法。
第一种方法,公有静态成员是个final
域:
public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { ... } public void leaveTheBuilding() { ... } }
第二种方法,公有的成员是个静态工厂方法:
public class Elvis { private static final Elvis INSTANCE = new Elvis(); private Elvis() { ... } public static Elvis getInstance() { return INSTANCE } public void leaveTheBuilding() { ... } }
但要提醒一点:享有特权的客户端可以借助AccessibleObject.setAccessible
方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候创建异常。
特殊说明:
AccessibleObject类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。
对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method 或 Constructor 对象来设置或获得字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。
也就是说享有特权的可以破坏单例模式:
public class MainClass { public static void main(String[] args) throws Exception { // Person person = new Person.Builder("xiaobai","18").sex("男").country("中国").occupation("程序员").build(); Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); System.out.println(s1 == s2); // 返回 true for(Constructor<?> c : s1.getClass().getDeclaredConstructors()) { c.setAccessible(true); // AccessibleObject Singleton s3 = (Singleton)c.newInstance(); System.out.println(s3 == s2); } } } class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() { } public static Singleton getInstance() { return INSTANCE; } }
结果:
修改构造器,让它在被要求创建第二个实例的时候创建异常。
class Singleton { private static int count = 0; private static final Singleton INSTANCE = new Singleton(); private Singleton() { if(count == 1) throw new RuntimeException ("只能初始化一次!"); count++; } public static Singleton getInstance() { return INSTANCE; } }
结果:
从Java 1.5发行版本起,实现Singleton还有第三种方法。只需编写一个包含单个元素的枚举类型:
// Enum singleton - the prefered approach public enum Elvis { INSTANCE; public void leaveTheBuilding() { ... } }
这种方法在功能上与公有方法相近,但是它更加简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。
注:
文章的内容大多是《Effective Java》书中第二章 第3条:用私有构造器或者枚举类型强化Singleton属性 的内容,文中红色部分是我对文章的补充。