readResolve를 사용하면, readObject가 만들어낸 인스턴스를 다른것으로 대체할 수 있다. 이때 readObject가 만들어낸 인스턴스는 gc대상이 된다.
public class Elvis implements Serializable {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {}
private String[] favoriteSongs = {"REAL"};
public void printFavorites() {
System.out.println(Arrays.toString(favoriteSongs));
}
private Object readResolve() {
return INSTANCE;
}
}
만약, readResolve를 인스턴스 통제 목적으로 이용한다면, 모든 필드를 transient로 선언해야 한다.
그렇지 않으면 readResolve 메서드가 수행되기 전에 역질렬화된 객체의 참조를 공격할 여지가 남는다.
공격 아이디어
transient가 아닌 참조 필드를 갖고 있다면, 해당 필드는 readResolve 메서드가 실행되기 전 역질렬화 된다.
조작된 스트림을 사용해 필드의 역직렬화된 인스턴스의 참조를 훔쳐올 수 있다.
필드를 transient 로 선언하여 위와 같은 공격을 피할 수 있다. 하지만, Elvis를 원소 하나짜리 enum 타입으로 바꾸면 위 과정들을 고민하지 않아도 된다. (하지만 reflection을 사용하면 모든 방어가 무력화된다)
결론
readResolve를 사용해 인스턴스 통제 목적으로 이용한다면, transient 등 신경쓸 부분이 많으니, enum을 통해 관리가 편하다.
readResolve도 무쓸모는 아니다. 컴파일 타임에 어떤 인스턴스들이 있는지 알 수 없는 상황이라면, enum을 쓸 수 없다.
Serializable
를 구현하는 순간, 더 이상 싱글턴이 아니다.readResolve
를 사용하면,readObject
가 만들어낸 인스턴스를 다른것으로 대체할 수 있다. 이때 readObject가 만들어낸 인스턴스는 gc대상이 된다.만약, readResolve를 인스턴스 통제 목적으로 이용한다면, 모든 필드를 transient로 선언해야 한다. 그렇지 않으면
readResolve
메서드가 수행되기 전에 역질렬화된 객체의 참조를 공격할 여지가 남는다.필드를 transient 로 선언하여 위와 같은 공격을 피할 수 있다. 하지만, Elvis를 원소 하나짜리 enum 타입으로 바꾸면 위 과정들을 고민하지 않아도 된다. (하지만 reflection을 사용하면 모든 방어가 무력화된다)
결론