직렬화

객체 직렬화란 자바가 객체를 바이트 스트림으로 인코딩하고(직렬화) 다시 객체를 재구성하는(역직렬화) 메커니즘이다.
직렬화된 객체는 다른 VM에 전송하거나 디스크에 저장한 후 나중에 역질렬화할 수 있다. 직렬화가 품고 있는 위험과 그 위험을 최소하하는 방법을 배워보자.

85. 자바 직렬화의 대안을 찾으라.

핵심 정리

직렬화는 위험하니 피해야 한다. 시스템을 밑바닥부터 설계한다면 JSON이나 프로토콜 버퍼 같은 대안을 사용하자.
신뢰할 수 없는 데이터는 역직렬화하지 말자. 꼭 해야 한다면 객체 역직렬화 필터링을 사용하되, 이마저도 모든 공격을 막아줄 수는 없음을 기억하자.
클래스가 직렬화를 지원하도록 만들지 말고, 꼭 그렇게 만들어야 한다면 정말 신경써서 작성해야 한다.

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

핵심 정리

Serializable은 구현한다고 선언하기는 아주 쉽지만, 그것은 눈속임일 뿐이다.
한 클래스의 여러 버전이 상호작용할 일이 없고 서버가 신뢰할 수 없는 데이터에 노출될 가능성이 없는 등, 보호된 환경에서만 쓰일 클래스가 아니라면 Serializable 구현은 아주 신중하게 이뤄져야 한다.
상속할 수 있는 클래스라면 주의사항이 더욱 많아진다.

87. 커스텀 직렬화 형태를 고려해보라.

핵심 정리

클래스를 직렬화하기로 했다면 어떤 직렬화 형태를 사용할지 심사숙고하기 바란다.
자바의 기본 직렬화 형태는 객체를 직렬화한 결과가 해당 객체의 논리적 표현에 부합할 때만 사용하고, 그렇지 않으면 객체를 적절히 설명하는 커스텀 직렬화 형태를 고안하라.
직렬화 형태도 공개 메서드를 설계할 때에 준하는 시간을 들여 설계해야 한다. 한번 공개된 메서드는 향후 릴리스에서 제거할 수 없듯이, 직렬화 형태에 포함된 필드도 마음대로 제거할 수 없다.
잘못된 직렬화 형태를 선택하면 해당 클래스의 복잡성과 성능에 영구히 부정적인 영향을 남긴다.

88. readObject 메서드는 방어적으로 작성하라.

핵심 정리

readObject 메서드를 작성할 때는 언제나 public 생성자를 작성하는 자세로 임해야 한다.
readObject는 어떤 바이트 스트림이 넘어오더라도 유효한 인스턴스를 만들어 내야 한다.
바이트 스트림이 진짜 직렬화된 인스턴스라고 가정해서는 안된다. 기본 직렬화 형태를 사용한 클래스든 커스텀 직렬화를 사용한 클래스든 모든 문제가 그대로 발생할 수 있다.

안전한 readObject 메서드를 작성하는 지침 요약
  • private이어야 하는 객체 참조 필드는 각 필드가 가리키는 객체를 방어적으로 복사하라. 불변 클래스 내의 가변 요소가 여기 속한다.
  • 모든 불변식을 검사하여 어긋나는 게 발견되면 InvalidObjectException을 던진다. 방어적 복사 다음에는 반드시 불변식 검사가 뒤따라야 한다.
  • 역직렬화 후 객체 그래프 전체의 유효성을 검사해야 한다면 ObjectInputValidation 인터페이스를 사용하라.
  • 직접적이든 간접적이든, 재정의할 수 있는 메서드는 호출하지 말자.

89. 인스턴스 수를 통제해야 한다면 readResolve보다는 열거 타입을 사용하라.

핵심 정리

불변식을 지키기 위해 인스턴스를 통제해야 한다면 가능한 한 열거 타입을 사용하자.
여의치 않은 상황에서 직렬화와 인스턴스 통제가 모두 필요하다면 readResolve 메서드를 작성해 넣어야 하고, 그 클래스에서 모든 참조 타입 인스턴스 필드를 transient로 선언해야 한다.

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

핵심 정리

제3자가 확장할 수 없는 클래스라면 가능한 한 직렬화 프록시 패턴을 사용하자.
이 패턴이 아마도 중요한 불변식을 안정적으로 직렬화해주는 가장 쉬운 방법일 것이다.