0%

单例与序列化日常

有两种教科书式的单例模式,并不能真正地实现单例

因为可以通过序列化,克隆,反射方式来攻击这种单例模式.

饿汉式:

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