[Java] Serialization과 관련하여 몰랐던 사실.

Java의 축복 중 하나는 Serializable을 상속하는 것 만으로 Serialization과 Deserialization을 자동으로 해준다는 점이다.

어제까지 Serialization을 자동으로 해준다는 사실은 축복이라고만 생각했다.  코드를 Refactoring하는 과정에서 발생한 문제를 통해 Serializable을 상속할 때 주의해야 할 사실 두가지를 알게됐다.

(Serializable은 축복이다. 단, 제대로 알고 쓰는 경우에만)

1. Serializable class를 상속하는 경우 serialVersionUID 값을 명시적으로 설정하라.

Serializable을 상속하는 Class의 경우 Class의 versioning 용도로 serialVersionUID 변수를 사용한다. 이때 serialVersionUID 값을 명시적으로 지정하지 않으면 Compiler가 계산한 값을 부여하는데 Serializable Class 또는 Outer Class에 변경이 있으면 serialVersionUID 값이 바뀌게 된다. 잠재적인 문제는 Serialize 할 때와 Deserialize 할 때의 serialVersionUID 값이 다르면 InvalidClassExceptions이 발생하여 저장된 값을 객체로 Restore 할 수 없다. 아래 문장이 포인트다.

serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected InvalidClassExceptions during deserialization.

The serialization runtime associates with each serializable class a version number, called a serialVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the receiver has loaded a class for the object that has a different serialVersionUID than that of the corresponding sender’s class, then deserialization will result in an InvalidClassException. A serializable class can declare its own serialVersionUID explicitly by declaring a field named “serialVersionUID” that must be static, final, and of type long:

If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification. However, it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected InvalidClassExceptions during deserialization. Therefore, to guarantee a consistent serialVersionUID value across different java compiler implementations, a serializable class must declare an explicit serialVersionUID value. It is also strongly advised that explicit serialVersionUID declarations use the private modifier where possible, since such declarations apply only to the immediately declaring class–serialVersionUID fields are not useful as inherited members.

해결책 : 만약 serialVersionUID 값을 설정하지 않은 상태로 release가 됐다면 기존에 Compiler가 자동으로 할당한 serialVersionUID 값을 찾아 명시적으로 설정하여 하위 호환성을 확보 할 수 있다.

 

 

2. Serializable를 상속하는 Class의 package가 변경되는 경우 Deserialize 시에 ClassNotFoundException이 발생한다.

소스코드를 Refactoring하거나 release시에 Proguard를 적용하고 있다면 Serializable Class의 Package가 변경 될 수 있다. 그리고 Serializable Class의 Package가 변경되면 Deserialize시에 ClassNotFoundException이 발생한다.

 

해결책 : ObjectOutputStream을 상속하는 Class를 만든 뒤, readClassDescriptor() 함수를 Override하여 아래와 같이 처리해준다. 이 방법은 이전의 Package를 알고 있는 경우에만 유효하다.

 

 

* References

http://stackoverflow.com/questions/10604672/java-objects-cannot-be-deserialized-after-the-packagename-of-a-class-has-been-ch

http://stackoverflow.com/questions/5305473/how-to-deal-with-a-java-serialized-object-whose-package-changed/5305751#5305751