有两种教科书式的单例模式,并不能真正地实现单例
因为可以通过序列化,克隆,反射方式来攻击这种单例模式.
饿汉式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class HungrySingleton implements Serializable { private static final HungrySingleton singleton = new HungrySingleton();
private HungrySingleton() { }
public static HungrySingleton getInstance() { return singleton; }
private String line;
public String getLine() { return line; }
public void setLine(String line) { this.line = line; }
@Override public String toString() { return "HungrySingleton{" + "line='" + line + '\'' + '}'; } }
|
懒汉式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class LazySingleton implements Serializable { private static LazySingleton singleton = null;
private LazySingleton() { }
public static LazySingleton getInstance() { if (singleton == null) { singleton = new LazySingleton(); } return singleton;
}
private String line;
public String getLine() { return line; }
public void setLine(String line) { this.line = line; }
@Override public String toString() { return "LazySingleton{" + "line='" + line + '\'' + '}'; } }
|
用序列化方式攻击饿汉式单例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class SingletonDemo { public static void main(String[] args) throws IOException, ClassNotFoundException { HungrySingleton singleton = HungrySingleton.getInstance(); singleton.setLine("singleton"); File file = new File("serializable.txt");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); oos.writeObject(singleton); oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); HungrySingleton deserializationSingleTon = (HungrySingleton) ois.readObject(); ois.close();
System.out.println(HungrySingleton.getInstance()); System.out.println(deserializationSingleTon); System.out.println(singleton == HungrySingleton.getInstance()); System.out.println(HungrySingleton.getInstance() == deserializationSingleTon); } }
|
得到的结果是:
1 2 3 4
| HungrySingleton{line='singleton'} HungrySingleton{line='singleton'} true false
|
ObjectInputStream源码中有一段:
1 2 3 4 5 6 7 8 9 10
| 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); } }
|
1.如果实例不为空
2.描述符内检测到含有readResolve方法
3.反序列化中没有异常发生
同时满足3个条件,才会通过invokeReadResolve()
反射获取实例
在HungrySingleton.java
中添加
1 2 3
| private Object readResolve() { return getInstance(); }
|
然后再运行SingletonDemo.java
,可以看到,反序列化前后对象比较结果为true了
1 2 3 4
| HungrySingleton{line='singleton'} HungrySingleton{line='singleton'} true true
|
但readResolve
这种方法在fastjson反序列化中行不通
有一种更简单的自带序列化机制的单例模式:
单元素枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public enum EnumSingleton { singleton; private String line;
public String getLine() { return line; }
public void setLine(String line) { this.line = line; }
public static EnumSingleton getInstance() { return singleton; }
@Override public String toString() { return "EnumSingleton{" + "line='" + line + '\'' + '}'; } }
|
把SingletonDemo.java
中的HungrySingleton替换为EnumSingleton运行,发现即使不做额外控制,枚举序列化前后的对象是一致的
1 2 3 4
| EnumSingleton{line='singleton'} EnumSingleton{line='singleton'} true true
|
即使在fastjson中,枚举单例反序列化前后也是保持一致的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class SingletonDemo { public static void main(String[] args) throws IOException, ClassNotFoundException { EnumSingleton singleton = EnumSingleton.getInstance(); singleton.setLine("singleton");
String singletonStr = JSON.toJSONString(singleton);
EnumSingleton deserializationSingleTon = JSON.parseObject(singletonStr, EnumSingleton.class);
System.out.println(EnumSingleton.getInstance()); System.out.println(deserializationSingleTon); System.out.println(singleton == EnumSingleton.getInstance()); System.out.println(EnumSingleton.getInstance() == deserializationSingleTon); } }
|
1 2 3 4
| EnumSingleton{line='singleton'} EnumSingleton{line='singleton'} true true
|