单例设计模式的实现方式有很多种,如饿汉式,懒汉式,双重检查锁,静态内部类,枚举等等,但是在平时的开发中,我们实现的单利模式是有一定的漏洞的,可以通过反射或者序列化以及反序列化获取不同的实例,虽然这个漏洞在系统运行的时候不会体现出来,但是在开发时也是值得注意的问题。
使用反射技术来获取不同的实例:
以下是一个简单的饿汉式的单利模式的代码实现:
package com.spring.designmodel;
import java.io.Serializable;
public class Singleton implements Serializable{
private static final long serialVersionUID = 1L;
private static final Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
}
当我们需要获取Singleton对象的时候,直接调用静态方法getInstance就可以了:
package com.spring.designmodel;
import java.io.IOException;
import java.lang.reflect.Constructor;
public class SingletonTest {
public static void main(String[] args) {
try {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1);
System.out.println(singleton2);
//使用反射获取对象实例
Class<Singleton> clazz1 = (Class<Singleton>) Class.forName("com.spring.designmodel.Singleton");
Constructor<Singleton> constructor1 = clazz1.getDeclaredConstructor(null);
constructor1.setAccessible(true);
Singleton singleton3 = constructor1.newInstance();
Class<Singleton> clazz2 = (Class<Singleton>) Class.forName("com.spring.designmodel.Singleton");
Constructor<Singleton> constructor2 = clazz2.getDeclaredConstructor(null);
constructor2.setAccessible(true);
Singleton singleton4 = constructor2.newInstance();
System.out.println(singleton3);
System.out.println(singleton4);
} catch (Exception e) {
e.printStackTrace();
}
}
}
但是学过反射的人都知道,通过反射技术也能获取到一个类的实例对象,即使它的构造函数时私有化的,我们也可以通过暴力访问来调用其构造函数,所以以上测试类的运行结果为:
com.spring.designmodel.Singleton@73fbaf73
com.spring.designmodel.Singleton@73fbaf73
com.spring.designmodel.Singleton@3f4f44
com.spring.designmodel.Singleton@3c6cf97c
可以看出通过调用getInstance方法获取到的实例是一样的,但是通过反射获取到的实例却是不同的,违反了单例设计模式的思想,那么我们应该怎么解决呢?我们只需要在私有的构造函数中加入一个判断即可:
package com.spring.designmodel;
import java.io.Serializable;
public class Singleton implements Serializable{
private static final long serialVersionUID = 1L;
private static final Singleton singleton = new Singleton();
private Singleton(){
if(null != singleton){
throw new RuntimeException();
}
}
public static Singleton getInstance(){
return singleton;
}
}
此时,我们再次启动测试类,获得到以下结果:
com.spring.designmodel.Singleton@73fbaf73
com.spring.designmodel.Singleton@73fbaf73
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at org.springsource.loaded.ri.ReflectiveInterceptor.jlrConstructorNewInstance(ReflectiveInterceptor.java:1075)
at com.spring.designmodel.SingletonTest.main(SingletonTest.java:19)
Caused by: java.lang.RuntimeException
at com.spring.designmodel.Singleton.(Singleton.java:13)
… 6 more
当然,就解决了使用反射技术来获取不同实例的问题了。
使用序列化及反序列化技术获取不同的实例:
如果我们的单例类实现了Serializable接口,那么这个类就能进行序列化和反序列化,测试代码如下:
package com.spring.designmodel;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SingletonTest {
public static void main(String[] args){
try {
Singleton singleton1 = Singleton.getInstance();
FileOutputStream fos = new FileOutputStream("d://singleton.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(Singleton.getInstance());
FileInputStream fis = new FileInputStream("d://singleton.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Singleton singleton2 = (Singleton) ois.readObject();
oos.close();
ois.close();
System.out.println(singleton1);
System.out.println(singleton2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果如下:
com.spring.designmodel.Singleton@139008b
com.spring.designmodel.Singleton@221c3dfe
我们发现进过序列化及反序列化之后对象的引用就改变了,显然也是违反了单例设计模式的思想的,跟踪readObject源码后,发现这个方法会先写出一个newInstance,然后判断这个对象中是否存在readResolve这个方法,如果不存在,那么直接返回这个newInstance,如果存在,那么就调用readResolve这个方法,将这个方法的返回值返回给readObject.源码片段如下:
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
由上可知,我们只需要在Singleton这个类中添加一个readResolve这个方法即可。
package com.spring.designmodel;
import java.io.Serializable;
public class Singleton implements Serializable{
private static final long serialVersionUID = 1L;
private static final Singleton singleton = new Singleton();
private Singleton(){
if(null != singleton){
throw new RuntimeException();
}
}
public static Singleton getInstance(){
return singleton;
}
public Object readResolve(){
return singleton;
}
}
再次启用测试类,运行结果如下:
com.spring.designmodel.Singleton@403729c5
com.spring.designmodel.Singleton@403729c5