相关文章:
- 每天学习一点点之从 SonarQube Bug 看对线程中断异常的处理
昨天组内同学在进行代码合并的时候发现了一个 SonarQube 异常:
其实我之前也遇到过这个异常,但觉得“这种异常很无聊”,毕竟让 Spring Bean 去序列化,这不是扯么,就“偷懒”没有仔细去看。晚上回到家越想越不得劲,毕竟留个未知问题睡觉是很难受的,于是又打开电脑去看了下 Sonar 的描述:
Fields in a “Serializable” class should either be transient or serializable
Fields in a
Serializable
class must themselves be eitherSerializable
ortransient
even if the class is never explicitly serialized or deserialized. For instance, under load, most J2EE application frameworks flush objects to disk, and an allegedlySerializable
object with non-transient, non-serializable data members could cause program crashes, and open the door to attackers. In general aSerializable
class is expected to fulfil its contract and not have an unexpected behaviour when an instance is serialized.This rule raises an issue on non-
Serializable
fields, and on collection fields when they are notprivate
(because they could be assigned non-Serializable
values externally), and when they are assigned non-Serializable
types within the class.Noncompliant Code Example
public class Address { //... } public class Person implements Serializable { private static final long serialVersionUID = 1905122041950251207L; private String name; private Address address; // Noncompliant; Address isn't serializable }
Compliant Solution
public class Address implements Serializable { private static final long serialVersionUID = 2405172041950251807L; } public class Person implements Serializable { private static final long serialVersionUID = 1905122041950251207L; private String name; private Address address; }
Exceptions
The alternative to making all members
serializable
ortransient
is to implement special methods which take on the responsibility of properly serializing and de-serializing the object. This rule ignores classes which implement the following methods:private void writeObject(java.io.ObjectOutputStream out) throws IOException private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
See
- MITRE, CWE-594 - Saving Unserializable Objects to Disk
- Oracle Java 6, Serializable
可以发现 Sonar 的描述已经说的很清楚了:在一个声明为 Serializable
的类中,所有的字段都应该是 Serializable
或 transient
,即使该类没有被显式地序列化或反序列化。
当一个对象被序列化时,它的所有字段(包括继承的字段)都必须是可以序列化的,也就是说它们必须要么实现了 java.io.Serializable
接口,要么被声明为 transient
。这是因为,如果一个对象包含一些不是 Serializable
的字段,那么这些字段在序列化过程中将不会被保存,从而可能导致在反序列化时丢失重要的数据或引发其他问题。为了确保对象在序列化过程中得到完全的描述,并且可以在将来被正确地反序列化,我们需要将所有字段都声明为 Serializable
或者将它们声明为 transient
。
在这块代码中,也就是说引用 WarehouseService
的类实现了 Serializable
,但是 WarehouseService
本身却没有实现 Serializable
,这可能会导致序列化问题。
例如,我们有两个类:Person
和 Address
,其中 Person
实现了 Serializable
接口,而 Address
没有:
class Address {
int id;
String street;
int zip;
}
class Person implements Serializable {
int id;
String name;
Address address;
}
如果我们尝试将一个 Person
对象序列化,就会出现异常,因为 Address
类中的字段不能被序列化:
Person p = new Person();
p.id = 1;
p.name = "John";
p.address = new Address();
// ...
try {
FileOutputStream fileOut = new FileOutputStream("/tmp/tmp.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(p);
} catch (IOException e) {
}
上述代码会抛出异常:
java.io.NotSerializableException: com.example.Address
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
解决这个问题的一种方法是在 Address
类中添加 implements Serializable
关键字,使其也成为可序列化的:
class Address implements Serializable {
int id;
String street;
int zip;
}
class Person implements Serializable {
int id;
String name;
Address address;
}
另一种方法是将 Address
类中的字段声明为 transient
,以防止它们被序列化:
class Address {
transient int id;
String street;
int zip;
}
class Person implements Serializable {
int id;
String name;
Address address;
}
在此示例中,Address
类中的 id
字段将会被忽略,不会出现在序列化的数据中。 需要注意的是,如果一个字段声明为 transient
,那么在反序列化时,它的值会被重置为其默认值。因此,如果你希望保留其值,在反序列化时应该手动设置。例如,我们可以在 Person
类中添加一个方法来设置地址的 ID:
public void setAddress(Address address) {
this.address = address;
if (address != null) {
this.address.setId(this.id);
}
}
另外,你还可以实现 private void writeObject(java.io.ObjectOutputStream out) throws IOException
和 private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
方法来控制如何序列化和反序列化对象。这两个方法通常被称为定制序列化方法,因为它们让你有机会自定义对象的序列化和反序列化方式。
总之,在设计类的时候,请注意一个关键点,如果你想要让一个类是可序列化的,那么它所有字段都必须是可序列化的。如果有字段不应该是序列化的,那么可以将它们声明为 transient
,或者使用定制序列化方法来处理它们。
欢迎关注公众号: