From d1901af70247fb13b9413bc9e5a0843bff4781aa Mon Sep 17 00:00:00 2001 From: KimDoubleB Date: Wed, 7 Apr 2021 20:53:24 +0900 Subject: [PATCH 1/2] docs: 90 --- ...3\234 0f799f9d9a254e35952729888c9122d0.md" | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 "12\354\236\245/[\354\235\264\355\216\231\354\236\220\353\260\224] \354\225\204\354\235\264\355\205\234 90 \354\247\201\353\240\254\355\231\224\353\220\234 \354\235\270\354\212\244\355\204\264\354\212\244 \353\214\200\354\213\234 0f799f9d9a254e35952729888c9122d0.md" diff --git "a/12\354\236\245/[\354\235\264\355\216\231\354\236\220\353\260\224] \354\225\204\354\235\264\355\205\234 90 \354\247\201\353\240\254\355\231\224\353\220\234 \354\235\270\354\212\244\355\204\264\354\212\244 \353\214\200\354\213\234 0f799f9d9a254e35952729888c9122d0.md" "b/12\354\236\245/[\354\235\264\355\216\231\354\236\220\353\260\224] \354\225\204\354\235\264\355\205\234 90 \354\247\201\353\240\254\355\231\224\353\220\234 \354\235\270\354\212\244\355\204\264\354\212\244 \353\214\200\354\213\234 0f799f9d9a254e35952729888c9122d0.md" new file mode 100644 index 0000000..8031153 --- /dev/null +++ "b/12\354\236\245/[\354\235\264\355\216\231\354\236\220\353\260\224] \354\225\204\354\235\264\355\205\234 90 \354\247\201\353\240\254\355\231\224\353\220\234 \354\235\270\354\212\244\355\204\264\354\212\244 \353\214\200\354\213\234 0f799f9d9a254e35952729888c9122d0.md" @@ -0,0 +1,186 @@ +스크린샷 2021-04-07 오후 8 52 01 + +# 아이템 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("프록시가 필요해요."); + } +} +``` + +
+ +```java +// Period +private Object writeReplace() { + return new SerializationProxy(this); +} +``` + +- Serialize -> 프록시 인스턴스 반환 +- 결코 바깥 클래스의 직렬화된 인스턴스를 생성해낼 수 없다 + +
+ + +```java +// Period +private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("프록시가 필요해요."); +} +``` + +- Period 자체의 역직렬화를 방지 -> 역직렬화 시도시, 에러 반환 + +
+ + +```java +// SerializationProxy +private Object readResolve() { + return new Period(start, end); +} +``` + +- Deserialize -> Object 생성 +- 공개된 API 만을 사용해 바깥 클래스의 인스턴스를 생성 → 생성자, 정적 팩터리 등을 통해 인스턴스를 생성 및 반환이 가능하다! + - 즉, 아이템 88에서 나오는 것처럼 불변식을 검사하는 수단이 필요가 없다! + - 생성자, 정적 팩터리 메서드에서 불변식 검사를 잘 해주고 있다면. + + +
+ + +--- + +## 역직렬화한 인스턴스와 원래의 직렬화된 인스턴스의 클래스가 달라도 정상 작동한다 + +말이 너무 어렵다.... + +→ 쉽게 말하면, + +- A 클래스의 인스턴스를 직렬화했다. +- 그 데이터를 가지고 역직렬화를 수행해 B 클래스의 인스턴스를 만들었다. (역직렬화 했다) +- 근데 정상작동한다 ! + +처음엔, 이해도 잘 안되고... 이게 왜...? 라는 생각이 들었다. +
+
+ +예를 보자. + +### EnumSet (Item 36) + +`EnumSet`은 생성자 없이 정적 팩터리들만 제공 + +- 단순히 생각하면 `EnumSet` 인스턴스를 반환하는 것 같지만, 열거 타입의 크기에 따라 다르다. +- 열거 타입의 원소의 개수가 + - 64개 이하면, `RegularEnumSet` 을 반환 + - 그보다 크면, `JumboEnumSet` 을 반환 + ![1](https://user-images.githubusercontent.com/37873745/113861202-e6c46b00-97e1-11eb-9806-20c405d4d4a1.png) + +
+ +### 시나리오 + +- 63개의 열거타입을 가진 EnumSet이 있다. +- 이를 직렬화하자. +- 그리고 원소 5개를 추가해 역직렬화하자. + +
+당연히 처음엔 `RegularEnumSet` 인스턴스 였다가, 나중엔 `JumboEnumSet` 로 하는 것이 더 효율적이고 좋을 것이다. + +- 직렬화 프록시를 이용하면, 원하는 방향대로 사용이 가능하다. +- 아래는 `EnumSet` 의 실제 코드 - 직렬화 프록시 패턴을 이용 + +```java +private static class SerializationProxy > implements java.io.Serializable +{ + // EnumSet의 원소 타입 + private final Class elementType; + + // EnumSet 내부 원소 + private final Enum[] elements; + + SerializationProxy(EnumSet set) { + elementType = set.elementType; + elements = set.toArray(ZERO_LENGTH_ENUM_ARRAY); + } + + // 새롭게 원소의 크기에 맞는 EnumSet 생성 + @SuppressWarnings("unchecked") + private Object readResolve() { + EnumSet 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"); +} +``` +
+
+ +## 직렬화 프록시 패턴의 한계 + +1. 클라이언트 멋대로 확장할 수 있는 클래스에는 적용할 수 없다. + +2. 객체 그래프에 순환이 있는 클래스에는 적용할 수 없다. + +3. (책엔 한계라고 적혀있지는 않지만) 속도가 느리다 + +- 책에서 말하길, `Period` 예제의 경우 방어적 복사를 사용하는 것보다 14% 느려졌다고 한다. \ No newline at end of file From fcdc7c02dcad36def4b790b1948dd3959e3ce2ac Mon Sep 17 00:00:00 2001 From: KimDoubleB Date: Wed, 7 Apr 2021 21:03:22 +0900 Subject: [PATCH 2/2] rename: 90 --- ...\230\353\235\274_\352\271\200\353\263\264\353\260\260.md" | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) rename "12\354\236\245/[\354\235\264\355\216\231\354\236\220\353\260\224] \354\225\204\354\235\264\355\205\234 90 \354\247\201\353\240\254\355\231\224\353\220\234 \354\235\270\354\212\244\355\204\264\354\212\244 \353\214\200\354\213\234 0f799f9d9a254e35952729888c9122d0.md" => "12\354\236\245/90_\354\247\201\353\240\254\355\231\224\353\220\234_\354\235\270\354\212\244\355\204\264\354\212\244_\353\214\200\354\213\240_\354\247\201\353\240\254\355\231\224_\355\224\204\353\241\235\354\213\234_\354\202\254\354\232\251\354\235\204_\352\262\200\355\206\240\355\225\230\353\235\274_\352\271\200\353\263\264\353\260\260.md" (98%) diff --git "a/12\354\236\245/[\354\235\264\355\216\231\354\236\220\353\260\224] \354\225\204\354\235\264\355\205\234 90 \354\247\201\353\240\254\355\231\224\353\220\234 \354\235\270\354\212\244\355\204\264\354\212\244 \353\214\200\354\213\234 0f799f9d9a254e35952729888c9122d0.md" "b/12\354\236\245/90_\354\247\201\353\240\254\355\231\224\353\220\234_\354\235\270\354\212\244\355\204\264\354\212\244_\353\214\200\354\213\240_\354\247\201\353\240\254\355\231\224_\355\224\204\353\241\235\354\213\234_\354\202\254\354\232\251\354\235\204_\352\262\200\355\206\240\355\225\230\353\235\274_\352\271\200\353\263\264\353\260\260.md" similarity index 98% rename from "12\354\236\245/[\354\235\264\355\216\231\354\236\220\353\260\224] \354\225\204\354\235\264\355\205\234 90 \354\247\201\353\240\254\355\231\224\353\220\234 \354\235\270\354\212\244\355\204\264\354\212\244 \353\214\200\354\213\234 0f799f9d9a254e35952729888c9122d0.md" rename to "12\354\236\245/90_\354\247\201\353\240\254\355\231\224\353\220\234_\354\235\270\354\212\244\355\204\264\354\212\244_\353\214\200\354\213\240_\354\247\201\353\240\254\355\231\224_\355\224\204\353\241\235\354\213\234_\354\202\254\354\232\251\354\235\204_\352\262\200\355\206\240\355\225\230\353\235\274_\352\271\200\353\263\264\353\260\260.md" index 8031153..39f3280 100644 --- "a/12\354\236\245/[\354\235\264\355\216\231\354\236\220\353\260\224] \354\225\204\354\235\264\355\205\234 90 \354\247\201\353\240\254\355\231\224\353\220\234 \354\235\270\354\212\244\355\204\264\354\212\244 \353\214\200\354\213\234 0f799f9d9a254e35952729888c9122d0.md" +++ "b/12\354\236\245/90_\354\247\201\353\240\254\355\231\224\353\220\234_\354\235\270\354\212\244\355\204\264\354\212\244_\353\214\200\354\213\240_\354\247\201\353\240\254\355\231\224_\355\224\204\353\241\235\354\213\234_\354\202\254\354\232\251\354\235\204_\352\262\200\355\206\240\355\225\230\353\235\274_\352\271\200\353\263\264\353\260\260.md" @@ -4,6 +4,7 @@ Serializable을 구현하기로 결정한 순간, 언어의 생성자 이외의 방법으로도 인스턴스를 생성할 수 있게 된다. + - 이전 아이템에서도 계속 언급되지만, 버그 및 보안 문제가 일어날 가능성이 커진다. 직렬화 프록시 패턴 (Serialization Proxy Pattern)을 이용하면 이러한 위험을 크게 줄여줄 수 있다. @@ -121,7 +122,9 @@ private Object readResolve() { - 열거 타입의 원소의 개수가 - 64개 이하면, `RegularEnumSet` 을 반환 - 그보다 크면, `JumboEnumSet` 을 반환 - ![1](https://user-images.githubusercontent.com/37873745/113861202-e6c46b00-97e1-11eb-9806-20c405d4d4a1.png) +
+ +![1](https://user-images.githubusercontent.com/37873745/113861202-e6c46b00-97e1-11eb-9806-20c405d4d4a1.png)