인스턴스 통제
- 예전 Item3에서 싱글턴 패턴을 소개했습니다. 아래는 그때 사용한 예제 코드입니다.
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {}
...
}
- 예전 Item 3에서 이야기했지만 저 Elvis 클래스는 선언에
Serializable
를 붙여넣는 순간 더 이상 싱글턴이 아니게 됩니다.
- 기본 직렬화를 사용하지 않고, 명시적으로 재정의한
readObject
를 제공하더라도 해결할 수 없으며 반드시 클래스가 초기화될 때 만들어진 인스턴스와는 다른 인스턴스를 반환하게 됩니다.
readResolve
- 다행히
readResolve()
메서드를 사용하면 readObject
가 만들어낸 인스턴스를 다른 것으로 대체할 수 있습니다.
- 역직렬화한 객체의 클래스가
readResolve()
메서드를 적절히 정의해뒀다면, 역직렬화 후 새로 생성된 객체를 인수로 해당 메서드가 호출되고, 이 메서드가 반환한 객체 참조가 새로 생성된 객체를 대신하여 반환됩니다.

- 이때 그림에서 나온 저 객체는 대부분의 경우 참조가 유지되지 않기 때문에 바로 GC 대상이 됩니다.
- 이를 구현한 Elvis 클래스는 다음과 같습니다.
...
private Object readResolve() {
return INSTANCE;
}
주의점
- 위의
readResolve()
메서드는 readObject()
에서 역직렬환 객체는 무시하고 클래스 초기화 시점에서 만들어진 INSTANCE
를 반환합니다.
- 여기서 알 수 있는 점은 역직렬한 객체의 데이터를 모두 버리고 초기화 시점에서 만들어진 객체의 데이터만을 유지하기 때문에 직렬화 할 때 실제 데이터가 들어갈 이유가 없습니다.
- 따라서 실제 데이터들은 직렬화 시 모두 무시되어야 하므로 모든 인스턴스 필드를
transient
로 선언해야 합니다.
- 이는 책에서도 강조하는 내용으로
readResolve()
메서드를 싱글턴과 같은 곳에서 인스턴스 통제 목적으로 사용한다면 객체 참조 타입 인스턴스 필드들은 모두 transient
로 선언해야 한다고 되어 있습니다.
- 만약 이를 따르지 않을 경우 이전 아이템에서 있었던
MutablePeriod
공격 방식과 유사하게 readResolve
메서드가 수행되기 이전에 역직렬화된 객체에 참조를 공격할 여지가 남습니다.
