Effective Java ‐ Item 90⚠️ - dnwls16071/Backend_Summary GitHub Wiki

아이템 90 - 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라.

  • 여태까지 공부한 내용을 토대로 봤을 때, Serializable 인터페이스를 구현하기로 결정한 순간 언어의 정상 메커니즘인 생성자 이외의 방법으로 인스턴스를 생성할 수 있게 된다.
  • 결국 버그와 보안 문제로 이어질 가능성이 매우 커진다.
  • 이런 위험을 크게 줄여줄 기법으로 바로 직렬화 프록시 패턴(Serialization Proxy Pattern)이 있다.
public final class Period implements Serializable {
  
    private final Date start;
    private final Date end;
  
    public Period(Date start, Date end) {
        if (this.start.compareTo(this.end) > 0) {
            throw new IllegalArgumentException(start + "가 " + end + "보다 늦다.");
        }
        this.start = new Date(start.getTime());
        this.end = new Date(end.getTime());
    }
  
    private Object writeReplace() {
        return new SerializationProxy(this);
    }
  
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
        throw new InvalidObjectException("프록시가 필요합니다");
    }
  
    //바깥 클래스의 논리적 상태를 정밀하게 표현하는 중첩 클래스(Period의 직렬화 프록시)
    private static class SerializationProxy implements Serializable {
  
        private static final long serialVersionUID = 234098243823485285L;
  
        private final Date start;
        private final Date end;
  
        SerializationProxy(Period p) {
            this.start = p.start;
            this.end = p.end;
        }
  
        private Object readResolve() {
            return new Period(start, end);
        }
    }
}
  • 가짜 바이트 스트림 공격과 내부 필드 탈취 공격을 프록시 수준에서 차단해준다.
  • 필드들을 final로 선언해도 되므로 Period 클래스를 진정한 불변으로 만들 수 있다.
  • 역직렬화때 유효성 검사를 하지 않아도 된다.
  • 역직렬화한 인스턴스와 원래의 직렬화된 인스턴스의 클래스가 달라도 정상 작동한다.

직렬화 프록시 패턴의 한계

  • 클라이언트가 멋대로 확장할 수 있는 클래스에는 적용할 수 없다.
  • 객체그래프 순환이 있는 클래스에는 적용할 수 없다.
  • 방어적 복사보다 느리다.

확장할 수 없는 클래스라면 가능한 직렬화 프록시 패턴을 사용하자.