在JDK1.5之前的单例实现方式有两种(懒汉式和饿汉式并无设计上的区别故看做一种),两者同是私有构
造器,导出静态成员变量,以便调用者访问。
第一种
package singleton;
public class Singleton {
//导出全局成员
public final static Singleton INSTANCE = new Singleton();
//私有构造
private Singleton(){}
}
私有构造器只会被调用一次,用于构建Singleton类中的INSTANCE 实例,由于构造器被私有化,并且没
有其他公开的构造器,所有能够保证在app中只会有一个Singleton实例。
真的是我们想的那样只会存在一个么?答案是否定的,可以使用java反射包提供的setAccessible()
方法去掉权限检查即可构造出实例对象
使用反射构造单例对象
final Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
//忽略检查
constructor.setAccessible(false);
//构造对象实例
final Object newInstance = constructor.newInstance();
System.out.println(newInstance == Singleton.INSTANCE); //false
}
现在单例类被反射轻松攻破了吧。
再看第二种基于工厂方法的单例
package singleton;
public class Singleton {
//导出全局成员
private final static Singleton INSTANCE = new Singleton();
//私有构造
private Singleton(){}
public static final Singleton getInstance(){
return INSTANCE;
}
}
该方式与第一种差别并不大,工厂方法的优势在于灵活性,在不改变API的前提下,我们可以修改该类
是否为单例,还是为每一个线程构建一个实例对象。同第一中方式相同也存在反射攻击的可能性,为了
防止反射攻击,需要对私有构造进行改写
package singleton;
import java.lang.reflect.Constructor;
public class Singleton {
//导出全局成员
public final static Singleton INSTANCE = new Singleton();
//私有构造
private Singleton(){
if (null != INSTANCE) {
throw new IllegalArgumentException("不能存在两个实例对象");
}
}
public static void main(String[] args) throws Exception {
final Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
//忽略检查
constructor.setAccessible(false);
//构造对象实例 此时这里回抛出异常
final Object newInstance = constructor.newInstance();
System.out.println(newInstance == Singleton.INSTANCE);
}
}
这样子也可以就可以保证不受反射的攻击啦。
在JDK1.5之后的版本提供了枚举关键字,提供了更加方便的单例创建方式
public enum Singleton {
INSTANCE;
}
三句代码搞定一个单例,并且无偿的提供序列化机制,绝对防止多实例的存在。
以上两种方式如果序列化,仅仅是实现serialiazble接口是不够的,为了维护和保证Singleton请提供
一个方法
public Singleton redResolve(){
return INSTANCE;
}