Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
HanaHww2 opened this issue Apr 25, 2023 · 2 comments
Assignees

Comments

@HanaHww2
Copy link
Contributor

No description provided.

@Tldkt
Copy link
Contributor

Tldkt commented May 2, 2023

1. 직렬화 프록시 패턴이란?

  • 직렬화를 위해 해당 클래스 안에 만든 private static 중첩 클래스 (생성자는 1개만 있어야 함. 바깥 클래스를 매개변수로 받는)
  • 이 때, 직렬화 대상 객체의 내부 상태를 저장하는 대신, 대리 객체의 상태를 저장
  • 대리 객체는 직렬화된 데이터를 역직렬화할 때, 직렬화 대상 객체를 생성하고 초기화
class Period implements Serializable {
    private final Date start;
    private final Date end;

    public Period(Date start, Date end) {
        this.start = start;
        this.end = end;
    }

    private static class SerializationProxy implements Serializable {
        private static final long serialVersionUID = 2123123123;
        private final Date start;
        private final Date end;

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

        /**
         * Deserialize 할 때 호출된다.
         * 오브젝트를 생성한다.
         */
        private Object readResolve() {
            return new Period(start, end);
        }
    }

    /**
     * 이로 인해 바깥 클래스의 직렬화된 인스턴스를 생성할 수 없다.
     * 직렬화할 때 호출되는데, 프록시를 반환하게 하고 있다.
     *
     * Serialize할 때 호출된다.
     */
    private Object writeReplace() {
        return new SerializationProxy(this);
    }

    /**
     * readObject, writeObject 가 있다면, 기본적으로 Serialization 과정에서
     * ObjectInputStream, ObjectOutputStream이 호출하게 된다.
     * 그 안에 커스텀 로직을 넣어도 된다는 것.
     */
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
        // readObject는 deserialize할 때, 그러니까 오브젝트를 만들 때인데.
        // 이렇게 해두면, 직접 Period로 역직렬화를 할 수 없는 것이다.
        throw new InvalidObjectException("프록시가 필요해요.");
    }
}

SerializationProxy**클래스는**Period` 객체를 인자로 받아서, 해당 객체의 시작일과 종료일을 저장

SerializationProxy 클래스는 readResolve() 메서드를 오버라이드하여, 역직렬화할 때 프록시 객체(SerializationProxy)를 실제 객체(Period)로 변환합니다. 이렇게 함으로써, 직렬화된 객체가 역직렬화될 때 Period 객체를 생성하도록 보장

또한, Period 클래스는 readObject() 메서드를 오버라이드하여, 역직렬화할 때 직접 Period 객체를 생성할 수 없도록 함

→ 직렬화된 객체를 역직렬화할 때, 프록시 객체(SerializationProxy)를 거쳐서 실제 객체(Period)를 생성하게 되므로, 객체의 불변성과 안전성을 보장할 수 있습니다.

2. 직렬화 프록시 패턴의 장점

  • 멤버 필드를 final로 선언할 수 있기 때문에 진정한 불변으로 만들 수 있음
  • 직렬화 프록시 패턴은 역직렬화한 인스턴스와 원래의 직렬화된 클래스가 달라도 정상적으로 동작

3. 직렬화 프록시 한계

  1. 클라이언트가 확장할 수 있는 클래스에는 적용 불가

확장 가능한 클래스는 하위 클래스가 생성될 때마다 이를 반영하여 새로운 직렬화 대상 클래스를 만들기 때문

  1. 객체 그래프 순환이 있는 클래스에는 적용 불가

직렬화 프록시는 객체 그래프에서 참조되는 모든 객체를 대상으로 직렬화를 수행하는데, 객체 그래프 순환이 있는 경우 무한루프에 빠질 가능성이 있기 때문

  1. 방어적 복사보다 느림

@HanaHww2
Copy link
Contributor Author

HanaHww2 commented May 2, 2023

  • 직렬화 프록시 패턴 활용
    • 바깥 클래스의 논리적 상태를 정밀하게 표현하는 중첩 클래스를 private static으로 둔다.
    • 직렬화가 생성자를 이용하지 않고도 인스턴스를 생성하는 기능을 제공하는 것을 막아서 보안 강화

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants