Skip to content

Commit

Permalink
[#90][B팀] 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라
Browse files Browse the repository at this point in the history
[#90][B팀] 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라
  • Loading branch information
ksy90101 authored Apr 10, 2021
2 parents d7f2df8 + fcdc7c0 commit 4dfbdc0
Showing 1 changed file with 189 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<img width="1576" alt="스크린샷 2021-04-07 오후 8 52 01" src="https://user-images.githubusercontent.com/37873745/113862264-250e5a00-97e3-11eb-8092-3d88f5dbdcfe.png">

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

Serializable을 구현하기로 결정한 순간, 언어의 생성자 이외의 방법으로도 인스턴스를 생성할 수 있게 된다.


- 이전 아이템에서도 계속 언급되지만, 버그 및 보안 문제가 일어날 가능성이 커진다.

직렬화 프록시 패턴 (Serialization Proxy Pattern)을 이용하면 이러한 위험을 크게 줄여줄 수 있다.

- 직렬화 프록시는 일반적으로 이전 아이템에서 나온 `readObject` 의 방어적 복사보다 강력하다.

## 직렬화 프록시 패턴

```java
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 -> Object 생성
private Object readResolve() {
return new Period(start, end);
}
}

// Serialize -> 프록시 인스턴스 반환
// 결코 바깥 클래스의 직렬화된 인스턴스를 생성해낼 수 없다
private Object writeReplace() {
return new SerializationProxy(this);
}

// Period 자체의 역직렬화를 방지 -> 역직렬화 시도시, 에러 반환
private void readObject(ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("프록시가 필요해요.");
}
}
```

<br>

```java
// Period
private Object writeReplace() {
return new SerializationProxy(this);
}
```

- Serialize -> 프록시 인스턴스 반환
- 결코 바깥 클래스의 직렬화된 인스턴스를 생성해낼 수 없다

<br>


```java
// Period
private void readObject(ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("프록시가 필요해요.");
}
```

- Period 자체의 역직렬화를 방지 -> 역직렬화 시도시, 에러 반환

<br>


```java
// SerializationProxy
private Object readResolve() {
return new Period(start, end);
}
```

- Deserialize -> Object 생성
- 공개된 API 만을 사용해 바깥 클래스의 인스턴스를 생성 → 생성자, 정적 팩터리 등을 통해 인스턴스를 생성 및 반환이 가능하다!
- 즉, 아이템 88에서 나오는 것처럼 불변식을 검사하는 수단이 필요가 없다!
- 생성자, 정적 팩터리 메서드에서 불변식 검사를 잘 해주고 있다면.


<br>


---

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

말이 너무 어렵다....

→ 쉽게 말하면,

- A 클래스의 인스턴스를 직렬화했다.
- 그 데이터를 가지고 역직렬화를 수행해 B 클래스의 인스턴스를 만들었다. (역직렬화 했다)
- 근데 정상작동한다 !

처음엔, 이해도 잘 안되고... 이게 왜...? 라는 생각이 들었다.
<br>
<br>

예를 보자.

### EnumSet (Item 36)

`EnumSet`은 생성자 없이 정적 팩터리들만 제공

- 단순히 생각하면 `EnumSet` 인스턴스를 반환하는 것 같지만, 열거 타입의 크기에 따라 다르다.
- 열거 타입의 원소의 개수가
- 64개 이하면, `RegularEnumSet` 을 반환
- 그보다 크면, `JumboEnumSet` 을 반환
<br>

![1](https://user-images.githubusercontent.com/37873745/113861202-e6c46b00-97e1-11eb-9806-20c405d4d4a1.png)

<br>

### 시나리오

- 63개의 열거타입을 가진 EnumSet이 있다.
- 이를 직렬화하자.
- 그리고 원소 5개를 추가해 역직렬화하자.

<br>
당연히 처음엔 `RegularEnumSet` 인스턴스 였다가, 나중엔 `JumboEnumSet` 로 하는 것이 더 효율적이고 좋을 것이다.

- 직렬화 프록시를 이용하면, 원하는 방향대로 사용이 가능하다.
- 아래는 `EnumSet` 의 실제 코드 - 직렬화 프록시 패턴을 이용

```java
private static class SerializationProxy <E extends Enum<E>> implements java.io.Serializable
{
// EnumSet의 원소 타입
private final Class<E> elementType;

// EnumSet 내부 원소
private final Enum<?>[] elements;

SerializationProxy(EnumSet<E> set) {
elementType = set.elementType;
elements = set.toArray(ZERO_LENGTH_ENUM_ARRAY);
}

// 새롭게 원소의 크기에 맞는 EnumSet 생성
@SuppressWarnings("unchecked")
private Object readResolve() {
EnumSet<E> result = EnumSet.noneOf(elementType);
for (Enum<?> e : elements)
result.add((E)e);
return result;
}

private static final long serialVersionUID = 362491234563181265L;
}

Object writeReplace() {
return new SerializationProxy<>(this);
}

private void readObject(java.io.ObjectInputStream stream)
throws java.io.InvalidObjectException {
throw new java.io.InvalidObjectException("Proxy required");
}
```
<br>
<br>

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

1. 클라이언트 멋대로 확장할 수 있는 클래스에는 적용할 수 없다.

2. 객체 그래프에 순환이 있는 클래스에는 적용할 수 없다.

3. (책엔 한계라고 적혀있지는 않지만) 속도가 느리다

- 책에서 말하길, `Period` 예제의 경우 방어적 복사를 사용하는 것보다 14% 느려졌다고 한다.

0 comments on commit 4dfbdc0

Please sign in to comment.