Skip to content

[ Item 86 ] Serializable을 구현할지는 신중히 결정하라 #85

@qkrqudcks7

Description

@qkrqudcks7

Serializable을 구현할지는 신중히 결정하라

Serializable

어떤 클래스의 인스턴스를 직렬화 할 수 있게 하려면 클래스 선언에 implements Serializable만 덧붙이면 된다.


Serializable 구현의 문제점

1. Serializable을 구현하면 릴리스한 뒤에는 수정하기 어렵다.

  • 클래스가 Serializable을 구현하면 직렬화된 바이트 스트림 인코딩(직렬화 형태)도 하나의 공개 API 된다.
  • 따라서 커스텀 직렬화 형태를 설계하지 않고 자바의 기본 방식을 사용하면 직렬화 형태는 최소 적용 당시 클래스의 내부 구현 방식에 영원히 묶여 버린다.
// 이런 클래스가 있다고 예를 들어보자
public class Member implements Serializable {
      private String name;
      private String email;
      private int age;
    // 생략
  }

위 클래스를 직렬화를 했을 때

ANyibG9nLmV4ABpWhhbi5YW0xLk1lbWJlcgAAA3b293YAABAg ...

위 클래스에 code 속성을 추가 한 다음 직렬화한 데이터를 역직렬화 하면 ?

public class Member implements Serializable {
      private String name;
      private String email;
      private int age;
      // code 속성을 추가
      private int code;
  }
  • java.io.InvalidClassException 발생한다. serialVersionUID 가 다르기 때문이다.
  • 위 상황처럼 뒤늦게 클래스 내부 구현을 손보면 원래의 직렬화 형태가 달라져 역직렬화에 실패한다.

해결 방법

serialVersionUID를 명시적으로 선언하여 클래스 변경으로 인한 역직렬화 문제를 해결할 수 있다.

public class Member implements Serializable {
      static final long serialVersionUID = 1L;
      private String name;
      private String email;
      private int age;
      // code 속성을 추가
      private int code;
  }

2. 버그와 보안 구멍이 생길 위험이 높아진다.

  • 객체는 생성자를 사용해 만드는게 기본인데, 기본 역직렬화를 사용하면 불변식 깨짐과 허가되지 않는 접근에 쉽게 노출된다.
  • 왜냐하면 역직렬화는 숨은 생성자인데 생성자가 만족해야 하는 불변식이나 객체 생성중 객체 내부에 공격자가 접근할 수 없도록 해야 한다는 것을 잊기 쉽다.

3. 해당 클래스의 신버전을 릴리스할 때 테스트 할 것이 늘어난다.

  • 직렬화 가능 클래스가 수정되면 신버전 인스턴스를 직렬화한 후 구버전으로 역직렬화 할 수 있는지, 그 반대도 가능한지 검사해야 한다.
  • 따라서 테스트 할 양이 직렬화 가능 클래스의 수와 릴리스 횟수에 비례해 증가한다.
  • 다만 클래스를 처음 제각할 때 커스텀 직렬화 형태를 잘 설계해놨다면 이러한 테스트 부담을 줄일 수 있다.

4. Serializable 구현 여부는 가볍게 결정할 사안이 아니다.

  • 단, 객체를 전송하거나 저장할 때 자바 직렬화를 이용하는 프레임워크용으로 만든 클래스라면 선택의 여지가 없다. Serializable을 반드시 구현해야 하는 다른 클래스의 컴포넌트로 쓰일 클래스도 마찬가지다.
  • 하지만 이를 구현하는데 따르는 비용이 적지 않으니, 클래스를 설계할 때마다 그 이득과 비용을 잘 저울질 해야 한다.

5. 상속용으로 설계된 클래스는 대부분 Serializable을 구현하면 안되며, 인터페이스도 대부분 Serializable을 확장해서는 안된다.

  • 다만 Serializable을 구현한 클래스만 지원하는 프레임워크를 사용하는 상황이라면 어쩔수 없다.
  • 대표적으로 상속용으로 설계된 클래스 중 Serializable을 구현한 예로 Throwble과 Component가 있다.

6. 내부클래스는 직렬화를 구현하지 말아야 한다.

  • 내부 클래스에는 바깥 인스턴스의 참조와 유효 범위 안의 지역변수 값들을 저장하기 위해 컴파일러가 생성한 필드들이 자동으로 추가된다.
  • 이 필드들이 클래스 정의에 어떻게 추가 됬는지 정의되지 않았기 때문에 기본 직렬화 형태가 분명하지 않아 이용 불가하다.
  • 단 정적 멤버 클래스는 Serializable을 구현해도 된다.

핵심정리

  • Serializable은 구현한다고 선언하기는 쉽지만, 그것은 눈속임일 뿐이다.
  • 보호된 환경에서 쓸 클래스가 아니라면 Serializable 구현은 아주 신중하게 이뤄져야 한다.
  • 상속할 수 있는 클래스라면 주의사항이 더 많아진다.
  • static 멤버 클래스 : Serializable 구현 가능
  • 익명 내부 클래스, 지역 내부 클래스 : Serializable 구현 불가능

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions