brunch

You can make anything
by writing

C.S.Lewis

by 이승현 Oct 15. 2018

직렬화(Serialization)

#74 Serializable 인터페이스를 분별력 있게 구현하자

Effective Java - 직렬화(Serialization)


#01 Serialization & Deserialization


객체 직렬화(object serialization)란, 객체를 바이트 스트림으로 인코딩(encoding)하고, 인코딩 된 바이트 스트림으로부터 객체를 복원하는 것을 말합니다.

직렬화 - 객체를 바이트 스크림으로 인코딩

역직렬화 - 바이트 스크림을 객체로 인코딩




#74 Serializable 인터페이스를 분별력 있게 구현하자


클래스의 인스턴스가 직렬화되기 위해서는 Serializable 인터페이스를 구현해야 합니다.

Serializable 인터페이스는 구현해야 하는 메서드가 없는 마커 인터페이스이기 때문에, 개발하는 입장에서는 이용하기 정말 간단합니다.


하지만 이에 상응하는 여러 가지 비용이 발생할 수 있습니다.




#01 유지 보수 문제


#02 InvalidClassException


만약 직렬화를 하고 나중에 클래스의 내부 구현을 변경(유지/보수)한다면, 변경으로 인해 역직렬 화가 되지 않을 수 있습니다.


1. Serializable 인터페이스를 구현한 Video 클래스입니다.

#03 Video class


2. 이를 직렬 화해서 별도의 파일(fileName)에 저장합니다.

#04 Serialization


3. 그리고 Video 클래스에 String subTitle 변수를 추가합니다.

#05 Video class


4. subTitle 변수를 추가하기 전, Video 객체의 바이트 스트림을 subTitle 변수가 추가된 Video 클래스의 객체로 역직렬화를 시도하면, 

#06 deserialization


5. 아래처럼 exception이 발생합니다.

serialVersionUID 값이 서로 달라서 발생하고 있습니다.

#07 InvalidClassException


serialVersionUID이란, 스트림 고유 식별자(stream unique identifier)로서 값 또한 가능한 모든 클래스는 고유의 식별 번호를 가지고 있습니다.

만약 static final long serialVersionUID를 명시적으로 선언하지 않으면, 시스템에서 자동으로 생성해줍니다.

그런데 만약 이 클래스를 변경하면, 자동으로 생성된 이 값이 변경되고 역직렬화시 InvalidClassException이 발생합니다.


이를 방지하기 위해, static final long serialVersionUID를 명시적으로 선언할 수 있습니다.

직접 선언을 하면, 클래스가 변경되어도 동일한 고유 식별 번호를 가지기 때문에, 클래스 변경으로 인한 역직렬화 문제가 발생하지 않습니다.


#08 serialVersionUID




#02 결함과 보안상의 허점을 증대


일반적으로 객체는 생성자를 통해 생성됩니다.

직렬화는 언어 영역을 벗어나는 방식(extralinguistic mechanism)으로 객체를 생성합니다.

역직렬화는 "감춰진 생성자"를 통해 객체를 생성합니다.


이 "감춰진 생성자"는 불변성의 손상이나 불법적인 접근에 객체를 그대로 방치하게 됩니다.

이에 대한 내용은 추후에 다루겠습니다.




#03 새 버전의 클래스 배포의 부담 증대


첫 번째 내용인 유지 보수 문제와 중복되는 내용이 있는데, 클래스가 개정될 때, 서로 직렬화/역직렬 화가 가능한지 확인해야 합니다.


단순히 예외나 에러 없이 직렬화 가능 여부만 확인하는 게 아니라, 의미적 호환성(semantic compatibility)도 문제없는지 확인해야 합니다.




#04 상속 문제


기본적으로 상속을 위해 설계된 클래스들은 Serializable 인터페이스를 구현할 필요가 없으며, 이러한 목적을 가진 인터페이스들도 Serializable 인터페이스를 확장(extends)할 필요 없습니다.

이는 상속을 통해 구현하는 입장에서는 큰 부담이기 때문입니다.


하지만 예외적으로 상속을 위해 설계된 클래스에서도 Serializable 인터페이스를 구현한 경우도 있습니다.

#09 Throwable class


Throwable : 원격 메서드 호출(remote method invocation) 시 발생된 예외를 서버로부터 클라이언트에 전달해야 하기 때문

Component : GUI 컴포넌트들이 전송, 저장, 복구될 수 있어야 하기 때문

HttpServlet : 세션 상태를 캐싱할 수 있어야 하기 때문




#04-01 슈퍼 클래스 = 상속 가능 + 직렬화 + 디폴트 값을 가지는 인스턴스 필드


개발을 하다 보면, 상속도 하면서 직렬 화도 가능한 클래스를 작성할 때가 있습니다.

이때 만약 디폴트 값을 가지는 인스턴스 필드도 있다면, readObjectNoData 메서드를 작성해야 합니다.


#10 No readObjectNoData


이펙티브 책을 보면 이해하기 어려운데 readObjectNoData 메서드가 호출되기 위해서는 위처럼 여러 단계가 필요합니다.


1. Serializable 인터페이스를 구현한 Sub 클래스입니다.

#11 Sub class


2. 이를 직렬 화해서 별도의 파일(fileName)에 저장합니다.

#12 Serialization Sub object


3. 그리고 디폴트 값(String defaultVersion = "1.0.0")을 가지는 Super 클래스와 이를 상속받는(extends) Sub 클래스를 생성합니다.

#13 Super class


4. Super 클래스를 상속받는 Sub 클래스의 객체로 역직렬화 해서 Super 클래스의 defaultVersion 필드 값을 출력하면, 

#14 Deserialization Sub object


5. 아래와 같이 String의 기본 값인 null이 출력됩니다.

#15 null


6. Super 클래스의 defaultVersion 값인 "1.0.0"을 그대로 출력하기 위해서는 readObjectNoData 메서드를 추가해야 합니다.

#16 readObjectNoData method


7. 이처럼 상속이 가능하고 직렬 화도 가능한 클래스에서 디폴트 값이 있는 인스턴스 필드를 제대로 이용하기 위해서는, readObjectNoData 메서드를 추가해야 합니다.

#17 1.0.0




#04-02 슈퍼 클래스 상속 가능 + 직렬화 지원 안 함 + 불변 규칙


자신은 직렬화를 지원하지 않지만, 상속을 통한 서브 클래스에서 직렬화를 지원하는 불변 클래스를 이용할 때도 주의할 점이 있습니다.


1. AbstractFoo 클래스는 위 조건들을 만족합니다.

추상 클래스이기 때문에 상속 가능

직렬화 지원 안 함(No Serializable interface)

AbstractFoo(int x, int y) 생성자를 통해서 int x, y를 초기화하는 불변 클래스

* 불변 클래스이기 때문에 final 키워드가 붙어야 하지만, 이 값들은 생성자가 아닌 초기화 메서드를 통해 값이 초기화되기 때문에 final이 붙지 않음.

#18 No serialization class


2.  서브 클래스의 readObject 메서드에서 직렬화를 통해 이 인스턴스에 접근하기 위해 protected 기본 생성자 추가

* 서브 클래스 인스턴스를 직렬 화할 때는 슈퍼 클래스의 기본 생성자가 자동으로 호출됨.

#19 protected 생성자


3.  서브 클래스의 readObject 메서드에서 이 인스턴스의 상태를 초기화하기 위해 매개 변수 없는 protected 초기화 메서드 추가

#20 protected 초기화 메서드


4. AbstractFoo 클래스의 모든 public-protected 메서드는 실행 전에 checkInit 메서드를 호출해야 합니다.

만약 initialize 초기화 메서드를 통해 초기화가 되지 않은 상태에서 x, y 값을 호출하는지 사전에 검사하기 위해서입니다.

#21 protected getX, getY 메서드


아래 클래스는 상태가 있고, 직렬화 불가능한 클래스(AbstractFoo class)의 직렬화 가능한 서브 클래스(Foo class)입니다.


이를 readObject, writeObject 메서드를 통해 직렬화, 역직렬화를 합니다.

이에 대한 내용은 다음에 기재하겠습니다.

#22 Foo sub class




#05 내부 클래스(inner class)


static 멤버 클래스 : Serializable 구현 가능

익명 내부 클래스, 지역 내부 클래스 : Serializable 구현 불가능


내부 클래스(inner class)는 외곽 인스턴스(enclosing instance)에 대한 참조와 지역 변수 값을 저장하는 , 컴파일러가 생성한 대용 필드(synthetic field)를 이용합니다.

이 대용 필드들이 클래스와 어떻게 대응되는지는 명시되어 있지 않기 때문에, 기본 직렬화 이용이 불가능합니다.




직렬화 즉, Serializable 인터페이스 구현은 고려해야 할 점이 많습니다.




매거진의 이전글 동시성(Concurrency)
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari