Effective Java를 읽고 정리한 정리본입니다.

📌 Item 90 : 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라

Serializable을 구현하기로 결정한다면, 언어의 정상 메커니즘인 생성자 이외의 방법으로 인스턴스를 생성할 수 있게 도니다.

이를 해결할 수 있는 방법이 바로 직렬화 프록시 패턴 (Serialization proxy pattern)이다.

예시를 먼저 보고 구현 방법을 살펴 보자.

🫧 직렬화 프록시 패턴 구현 예시

✨ inner 클래스

private static class SerializationProxy implements Serializable {
  private final Date start;
  private final Date end;

  SerializationProxy(Period p) {
    this.start = p.start;
    this.end = p.end;
  }

  private static final long serialVersionUID = 4324521489;
}

✨ outer class

다음의 writeReplace 메서드를 추가한다.

이 메서드는 범용적이므로 직렬화 프록시를 사용하는 모든 클래스에 복사해 쓰면 된다.

private Object writeReplace() {
  return new SerializationProxy(this);
}

🫧 직렬화 프록시 패턴 구현 방법

✨1. 바깥 클래스의 논리적 상태를 정밀하게 표현하는 중첩 클래스를 설계해 private static으로 선언한다.

이 중첩 클래스가 바로 바깥 클래스의 직렬화 프록시이다.

이때 주의사항이 몇 가지 존재한다.

  1. 중첩 클래스의 생성자는 단 하나여야 한다.
  2. 중첩 클래스의 생성자는 바깥 클래스를 매개변수로 받아야 한다.
  3. 바같 클래스와 직렬화 프록시 모두 Serializable을 구현한다고 선언해야 한다.

✨ 2. 바깥 클래스에 다음의 writeReplace 메서드를 추가한다.

private Object writeReplace() {
  return new SerializationProxy(this);
}

이 메서드는 자바의 직렬화 시스템이 바깥 클래스의 인스턴스 대신 SerializationProxy의 인스턴스를 반환하게 하는 역할을 한다.

달리 말해, 직렬화가 이뤄지기 전에 바깥 클래스의 인스턴스를 직렬화 프록시로 변환해준다.

만약 공격자가 불변식을 훼손하고자 한다면 다음과 같은 메서드를 추가하면 된다.

private void readObject(ObjectInputStream stream) throws InvalidObjectException {
  throw new InvalidObjectException("프록시가 필요합니다.");
}

✨ 3. 바깥 클래스와 논리적으로 동일한 인스턴스를 반환하는 readResolve 메서드를 SerializationProxy 클래스에 추가한다.

이 메서드는 역직렬화 시에 직렬화 시스템이 직렬화 프록시를 다시 바깥 클래스의 인스턴스로 변환하게 해준다.

대개의 직렬화는 생성자를 이용하지 않고도 인스턴스를 생성하는 기능을 제공하는데, 이 패턴은 이런 언어도단적 특성을 상당 부분 제거한다.

즉, 일반 인스턴스를 만들 때와 똑같은 생성자, 정적 팩터리, 혹은 다른 메서드를 사용해 역직렬화된 인스턴스를 생성하는 것이다.

💡 Period.SerializationProxy용 readResolve 메서드

private Object readResolve() {
  return new Period(start, end); // public 생성자 사용
}

🫧 직렬화 프록시 패턴의 장점

  • 역직렬화한 인스턴스와 원래의 직렬화된 인스턴스의 클래스가 달라도 정상 작동한다.

예를 들면, 다음과 같다.

EnumSet의 인스턴스는 사실 열거 타입의 크기에 따라 두 하위 클래스 중 하나의 인스턴스를 반환한다.

  • 열거 타입의 원소가 64개 이하 : RegularEnumSet
  • 열거 타입의 원소가 64개 이상 : JumboEnumSet

이를 통해 직렬화와 역직렬화 시 인스턴스의 클래스가 달라도 정상 작동할 수 있다는 사실을 알 수 있다.

🫧 직렬화 프록시 패턴의 한계

  1. 클라이언트가 멋대로 확장할 수 있는 클래스에는 적용할 수 없다.
  2. 객체 그래프에 순환이 있는 클래스에도 적용할 수 없다.
  3. 느리다.

그럼에도 장점이 크므로 긍정적으로 직렬화 프록시 패턴 도입을 고려하자!