单例模式指的是一个类只有一个对象,通过一些措施达到达到这个目的。但是反射和反序列化可以获得多个不同的对象。
先简单的认识一下单例模式
一:单例模式
通过私有构造器,声明一个该类的静态对象成员,提供一个获得对象的静态方法实现单例模式。单列模式有饿汉式和懒汉式,饿汉式是声明的同时就为该对象赋值。
懒汉式指的是使用到的时候再创建。虚拟机的实现会保证:类加载会确保类和对象的初始化方法在多线程场景下能够正确的同步加锁,即饿汉式声明并赋值是原子操作,不会存在同步问题。懒汉式为了应付同步问题出现了加锁,内部类延时加载等。
单例模式博客地址:http://www.cnblogs.com/tfper/p/9890971.html
二:单例模式存在的漏洞
1.反射破解单例模式
java的访问控制是停留在编译层的,也就是它不在在class文件中保留下任何痕迹,只在编译的时候进行访问控制的检查。通过反射的手段可以访问类中的成员,比如私有构造方法。
我们先写一个简单的懒汉式的类:
package cnblogs.bean; public class Singleton {
private static Singleton sl; //懒汉式 private Singleton() {} public static Singleton getInstance() {
if(sl == null)
sl = new Singleton();
return sl;
}
}
通过反射获取单例对象。
package cnblogs.test; import java.lang.reflect.Constructor; import cnblogs.bean.Singleton; public class Test2 { @SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
//正常获得单例模式对象
Singleton s1 = Singleton.getInstance();
System.out.println(s1); //通过反射获得单例模式对象
Class<Singleton> cl = (Class<Singleton>)
Class.forName("cnblogs.bean.Singleton");
Constructor<Singleton> constructor = cl.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton s2 = constructor.newInstance(); System.out.println(s2);
System.out.println("s1 == s2? " + (s1 == s2));
} }
控制台输出:
cnblogs.bean.Singleton@6d06d69c
cnblogs.bean.Singleton@7852e922
s1 == s2? false
观察一下就可以发现两种方式获得的单例模式不一样,违反了单例模式。
破解方式:
我们观察反射获取单例的代码,发现它还是调用了私有的构造方法获取对象【声明为私有的构造方法就是为了不让类外直接new对象】。如果只让私有的构造器只能调用一次就可以避免反射。
package cnblogs.bean; public class Singleton {
private static Singleton sl; private Singleton() {
//如果sl不为空即这不是第一次调用该构造器
if(sl != null)
throw new RuntimeException();
} public static Singleton getInstance() {
if(sl == null) {
sl = new Singleton();
}
return sl;
}
}
再次调用上面的反射测试类看控制台输出:
cnblogs.bean.Singleton@6d06d69c
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at cnblogs.test.Test2.main(Test2.java:20)
Caused by: java.lang.RuntimeException
at cnblogs.bean.Singleton.<init>(Singleton.java:10)
... 5 more
三:反序列化获得单例模式对象
单例类:
package cnblogs.bean; import java.io.Serializable;
/**
* 序列化必须实现Serializable接口,否则序列化时会报错
*
*/
public class Singleton implements Serializable{ private static final long serialVersionUID = 1L; private static Singleton sl; private Singleton() {
//如果sl不为空即这不是第一次调用该构造器
if(sl != null)
throw new RuntimeException();
} public static Singleton getInstance() {
if(sl == null) {
sl = new Singleton();
}
return sl;
}
}
测试类:
package cnblogs.test; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import cnblogs.bean.Singleton; public class Test3 { public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
System.out.println(s1);
//先序列化后反序列化
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(s1); InputStream is = new ByteArrayInputStream(os.toByteArray());
ObjectInputStream ois = new ObjectInputStream(is);
Singleton s2 = (Singleton) ois.readObject(); System.out.println(s2);
System.out.println("s1 == s2? " + (s1 == s2));
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} } }
控制台输出:
cnblogs.bean.Singleton@6d06d69c
cnblogs.bean.Singleton@42a57993
s1 == s2? false
解决方法:
package cnblogs.bean; import java.io.Serializable;
/**
* 序列化必须实现Serializable接口,否则序列化时会报错
*
*/
public class Singleton implements Serializable{ private static final long serialVersionUID = 1L; private static Singleton sl; private Singleton() {
//如果sl不为空即这不是第一次调用该构造器
if(sl != null)
throw new RuntimeException();
} public static Singleton getInstance() {
if(sl == null) {
sl = new Singleton();
}
return sl;
} /**
* 反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要在创建新的对象!
* @return
*/
private Object readResolve() {
return getInstance();
}
}
反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要在创建新的对象!
运行效果:
cnblogs.bean.Singleton@6d06d69c
cnblogs.bean.Singleton@6d06d69c
s1 == s2? true