Java中可以使用反射来创建类的实例,即使类的构造方法私有时,也可以创建,这样就可能导致单例模式被破坏。比如下面非常纯的单例模式:
//非常普通的单例模式
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
使用下面方法破坏单例:
public class SingletonKiller {
public static void main(String[] args) throws Exception {
/* 可以使用相对路径,同一个包中可以不用带包路径 */
Class s = Class.forName("com.piaohan.pattern.singleton.Singleton");
Constructor ct = s.getDeclaredConstructor();
/* true:使java不检查访问限制,即可以使用private方法 */
ct.setAccessible(true);
System.out.println(Singleton.getInstance());
System.out.println(ct.newInstance());
System.out.println(Singleton.getInstance() == ct.newInstance());
}
}
结果如下:
com.piaohan.pattern.singleton.Singleton@67f1fba0
com.piaohan.pattern.singleton.Singleton@3fbefab0
false
反射破坏了单例模式,但是实际开发中影响不大,项目约定好了不允许这样用就行了,很少有人会故意这么写,但是可以考虑使用这种方式结合工厂模式创建单例类,比如把所有要创建的单例类构造函数设置成私有,而正常写逻辑就行。代码如下:
//无状态业务逻辑
public class SingletonService {
// 私有构造函数,防止外部实例化
private SingletonService() {
}
// 业务逻辑1
public void method1() {
}
// 业务逻辑2
public void method2() {
}
}
使用下面工厂生成具体的逻辑类实例对象:
//单例工厂
public class SingletonFactory {
private static Map<Class<?>, Object> objCache = new HashMap<Class<?>, Object>();
public synchronized static Object getInstance(Class<?> clazz) throws Exception {
Object singleton = objCache.get(clazz);
if (singleton == null) {
singleton = createInstance(clazz);
objCache.put(clazz, singleton);
}
return singleton;
}
private static Object createInstance(Class<?> clazz) throws Exception {
Constructor ct = clazz.getDeclaredConstructor();
ct.setAccessible(true);
return ct.newInstance();
}
}
这样,就不用每个类都手动写一个单例模式了,当然,这里只是思路,实际开发中要考虑性能,并发等问题。如果能够使用spring,就可以替代类似这个功能了。