Java序列化那些事

最近在入门python,然后边学的时候对java 的知识点也有一些回顾。最近又碰到单例的安全问题,所以复习下序列化知识。方案有两种,比较简单暴力的是直接实现Serializable,稍微复杂一点的是实现Externalizable(需要手动的去序列化哪些值)。需要注意的是实现Externalizable的类需要有一个public 无参构造函数(会默认调用无参构造函数)。重点说说Serializable里面需要注意的东西。

用一个demo 来验证问题。是一个Socket 通信的代码,分Client 和Server。

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//Server 端,监听3400端口来的信息
public class ServerTest extends Thread {
private final int serverPort = 3400;
private ServerSocket server;
public ServerTest() {
try {
server = new ServerSocket(serverPort);
System.out.println("正在监听3400端口");
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
Socket socket = null;
ObjectInputStream in;
while (true) {
try {
synchronized (server) {
socket = server.accept();
}
System.out.println("当前的连接是:"
+ socket.getInetAddress().toString());
socket.setSoTimeout(20000);
in = new ObjectInputStream(socket.getInputStream());
ObjectSeri data = (ObjectSeri) in.readObject();
System.out.println("The name is:"
+ data.getName()
+ " and age is: "
+ data.getAge()
+ " and sex: "
+ data.getSex()
+ " and test: "
+ data.test);
in.close();
in = null;
socket.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
public static void main(String args[]) { // 开启一个Socket 用来监听本机的3400 端口号
(new ServerTest()).start();
}
}
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
35
36
// Client 端,用来发送消息
public class TestClient {
private String address = "127.0.0.1";
private int port = 3400;
public TestClient() {
// Prepare the data need to transmit
ObjectSeri data = new ObjectSeri();
data.setName("Egos");
data.setAge("18");
data.setSex("男");
data.test = "test";
Socket client = new Socket();
InetSocketAddress adr = new InetSocketAddress(this.address, this.port);
try {
client.connect(adr, 10000);
ObjectOutputStream out = new ObjectOutputStream(
client.getOutputStream());
// send object
out.writeObject(data);
out.flush();
out.close();
out = null;
data = null;
client.close();
client = null;
} catch (java.io.IOException e) {
System.out.println("IOException :" + e.toString());
}
}
public static void main(String[] args) {
new TestClient();
}
}

最后就是一个Serializable的对象。

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class ObjectSeri implements Serializable {
private String name;
private String age;
private transient String sex;
public static String test;
public ObjectSeri() {
System.out.println("ObjectSeri");
}
public String getName() {
return name;
}
public String getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(String age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
System.out.println("readObject " + this);
s.defaultReadObject();
sex = (String) s.readObject();
test = (String) s.readObject();
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
System.out.println("writeObject " + this);
s.defaultWriteObject();
s.writeObject(sex);
s.writeObject(test);
}
}

1. 序列化错误

java.io.InvalidClassException:(可以通过Server 先运行然后修改序列化对象的内容以后运行Client 来验证)是序列化和反序列化的时校验错误。解决办法是手动定义一个serialVersionUID。不过最好在设计可序列化对象的时候谨慎,经常变化还是可能出问题。

2. transient

transient可以修饰成员变量,这样系统默认就不会序列化这个该成员变量。

3. static

static修饰的成员变量统默认也不会序列化。

4. 顺序

当要手动的序列化transientstatic的时候就需要注意read 和write 的顺序了。

5. 是否是同一个对象

序列化时的对象和反序列化的对应不是同一个,并且实现Serializable接口的对象没有调用构造函数。

6. 单例的时候保证只有一个实例。

1
2
3
private Object readResolve() {
return getInstance();
}