ATDD 미션을 수행하는 중 커스텀 Exception을 만들 일이 필요했고, 기존에 구현된 UnAuthenticationException 그리고 UnAuthorizedException과 같은 커스텀 Exception에 serialVersionUID
가 있는 것을 발견했다. 그것도 같은 long 타입의 값을 가지고 있었다.
private static final long serialVersionUID = 1L;
그래서 내가 구현하고자 하는 커스텀 Exception 역시 serialVersionUID를 적어줘야하나 그리고 Serializable을 구현하는 다른 클래스의 serialVersionUID 값이 서로 같아도 되는가에 대한 궁금증이 생겨 serialVersionUID가 무엇을 의미하는지 알아보고자 한다.
serialVersionUID에 대해 이해하기 전 직렬화와 역직렬화에 대한 개념을 우선 이해하고 넘어가자.
직렬화란?
데이터 객체를 쉽게 전송할 수 있는 형식인 일련의 바이트로 변환하는 프로세스를 말한다. 이렇게 직렬화된 데이터는 다른 데이터 저장소나 응용 프로그램 또는 다른 대상으로 전달될 수 있다.
자바 시스템 내부에서 사용되는 Java Obejct 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터를 변환하는 기술을 자바 직렬화라고 한다. 시스템적으로 이야기하자면 JVM의 메모리에 상주되어 있는 Java Object를 바이트 형태로 변환하는 기술
역직렬화란?
직렬화된 일련의 바이트 형식의 데이터를 데이터 객체로 변환하는 프로세스를 말한다. 직렬화의 역 과정
자바 시스템적으로 이야기하면 직렬화된 바이트 형태의 데이터를 Java Object로 변환해서 JVM에 상주시키는 형태를 이야기한다.
자바 기본 타입과 java.io.Serializable 인터페이스를 상속하도록 만든 객체를 직렬화 할 수 있는 기본 조건으로 가진다.
SerialVersionUID는 역직렬화 시 해당하는 클래스의 버전이 맞는지를 확인하는 중요한 장치이다.
예시를 하나 들어보자.
다음과 같은 Serializable 인터페이스를 구현하는 Memeber 클래스가 존재한다.
public class Memeber implements Serializable {
private String name;
private int age;
...
}
Member 클래스를 인스턴스화하여 해당 객체를 직렬화 시키고, Member 클래스의 구조를 다음과 같이 변경한다.
public class Member implements Serializable {
private String name;
private int age;
private int height;
...
}
이전에 직렬화된 데이터를 역직렬화 시킨다면 java.io.InvalidClassException이 발생한다. 이유가 무엇일까?
직렬화 시키기 전 Member 클래스의 serialVersionUID와 Member 클래스의 구조를 변경시킨 후 serialVersionUID가 달라졌기 때문이다.
serialVersionUID는 직접 기술하지 않아도 내부적으로 serialVersionUID 정보가 추가된다. 구조가 달라졌으니 serialVersionUID가 달라진 것이다. 직접 serialVersionUID 값을 관리해주어야 클래스 변경 시 혼란을 줄일 수 있다.
Serializable을 구현하는 클래스들의 serialVersionUID 값이 서로 같아도 되냐에 대한 답은 같아도 된다. serialVersionUID 사용의 이유는 위에서 찾을 수 있다.
하지만 이외에도 serialVersionUID 값을 신경써야 할 부분이 있다.
serialVersionUID 값이 동일할 때에도 문제가 발생하는 경우를 알아보자.
멤버 변수명은 같은데 멤버 변수 타입이 변경 되었을 때 (자바 직렬화는 타입에 상당히 엄격하다.)
에러 발생
직렬화 자바 데이터에 존재하는 멤버 변수가 삭제 되었을 때
에러가 발생하지는 않지만 값 자체가 사라진다.
멤버 변수가 추가 되었을 때
에러를 발생하지 않고, 추가된 데이터는 기본값으로 초기화된다.
멤버 변수의 이름이 바뀔 때
오류는 발생하지 않지만 값이 할당되지 않는다.
접근 지정자의 변경
접근 지정자의 변경은 직렬화에 영향을 주지 않는다.
static과 transient
static 멤버를 직렬화 후 non-static 멤버로 변경하게 되는 경우 직렬화된 값은 무시된다.
transient 키워드는 직렬화 대상에서 제외하는 선언이므로 역직렬화 시에 transient 선언을 제외하더라도 값은 채워지지 않는다.
사실 이러한 관점에서 직렬화를 사용할 때는 자주 변경될 소지가 있는 클래스의 객체에는 사용하지 않는 것이 좋다. 프레임워크 또는 라이브러리에서 제공하는 클래스의 객체도 버전업을 통해 SerialVersionUID
가 변경될 경우가 있으므로 예상하지 못한 오류가 발생할 수 있다.
<원문 참조>
https://hazelcast.com/glossary/serialization/
https://hazelcast.com/glossary/deserialization/
https://techblog.woowahan.com/2550/
https://techblog.woowahan.com/2551/
https://docs.oracle.com/javase/10/docs/specs/serialization/class.html