Java

[Effective Java] 12-1. 직렬화

noahkim_ 2025. 1. 3. 03:41

조슈아 블로크 님의 "Effective Java" 책을 정리한 포스팅 입니다.

 

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

자바 직렬화

  • 객체의 상태를 바이트 스트림으로 변환하여 저장하거나 전송할 수 있게 하는 기술
  • 역직렬화를 통해 객체로 복원 가능

 

위험성
  • 보안
    • 원격코드 실행
    • 신뢰하지 못하는 스트림을 역직렬화 하면 공격으로 이어짐
  • 성능
    • DDoS: 큰 용량의 데이터를 역직렬화 하는데 많은 서버 리소스가 소모됨

 

직렬화 대안

JSON 
public class JsonExample {
    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        
        // 객체를 JSON으로 직렬화
        Person person = new Person("Noah", 30);
        String jsonString = mapper.writeValueAsString(person);
        System.out.println(jsonString); // {"name":"Noah","age":30}
        
        // JSON을 객체로 역직렬화
        Person deserializedPerson = mapper.readValue(jsonString, Person.class);
        System.out.println(deserializedPerson);
    }
}

class Person {
    public String name;
    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
  • 브라우저-서버 통신용
  • 텍스트 기반 (사람이 읽을 수 있음)

 

Protocol Buffer
Person person = Person.newBuilder().setName("Noah").setAge(30).build();

// 객체를 바이트 배열로 직렬화
byte[] serializedData = person.toByteArray();

// 바이트 배열을 객체로 역직렬화
Person deserializedPerson = Person.parseFrom(serializedData);
System.out.println(deserializedPerson.getName()); // Noah
  • 서버 간 통신용
  • 이진 표현 (성능 좋음)
  • 스키마용 사용 가능

 

역직렬화 필터링

화이트리스트 사용
SWaT
  • 직렬화 위험 요소를 분석하고 문제를 탐지 및 차단하는 툴

 

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

Serializable

  • 클래스 직렬화 시 사용하는 마커 인터페이스
  • 직렬화 지원을 하는데 간단해 보이지만, 매우 값비싼 일임

 

고려사항

릴리즈 한 뒤 수정 어려움
  • 직렬화된 데이터는 클래스의 구조에 강하게 의존함
  • 클래스 구조를 변경하면 호환성 문제 발생

 

버그와 보안 구멍이 생길 위험이 높음
  • 신뢰하지 못하는 스트림을 역직렬화 시 보안 취약점이 초래됨

 

신버전을 릴리즈할 때 테스트할 것이 늘어남
  • 모든 변경사항에 대해 직렬화 호환성 테스트가 필요

 

주의사항

상속용으로 설계된 클래스는 구현하면 안됨
  • 상위 클래스 직렬화도 하위 클래스에서 수행해야 하므로 직렬화 부담이 늘어남
  • 하위 클래스는 직렬화 호환성을 유지해야 하므로 설계가 제한됨

 

내부 클래스는 직렬화 구현하면 안됨
  • 내부 클래스는 외부 클래스의 참조를 포함하고 있어 직렬화하기 어렵고 비효율적

 

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

기본 직렬화 형태의 문제점

공개 API가 현재의 내부 표현 방식에 영구히 묶임
  • 기본 직렬화는 객체의 모든 필드를 직렬화 함
  • 클래스 설계를 변경하면 지곤의 직렬화된 데이터와의 호환성 문제 발생

 

너무 많은 공간을 차지할 수 있음
  • 직렬화에 불필요한 데이터도 포함될 수 있음

 

시간이 많이 걸림
  • 모든 필드를 직렬화하므로 객체 그래프를 탐색해야 함

 

스택 오버플로우를 일으킬 수 있음
  • 객체 그래프가 순환 참조를 포함하면 무한루프에 빠짐

 

고려 사항

writeObject()
public class CustomExample implements Serializable {
    private transient String tempData; // 직렬화 제외
    private String permanentData;

    private void writeObject(ObjectOutputStream out) throws Exception {
        out.defaultWriteObject(); // 기본 직렬화
        out.writeObject(tempData != null ? tempData.toUpperCase() : null); // 사용자 정의 직렬화
    }
}
  • 직렬화 데이터를 사용자 정의하여 효율적으로 저장

 

readObject()
public class SafeExample implements Serializable {
    private String data;

    private void readObject(ObjectInputStream in) throws Exception {
        in.defaultReadObject(); // 기본 역직렬화
        if (data == null || data.isEmpty()) {
            throw new IllegalArgumentException("Invalid data"); // 불변식 보장
        }
    }
}
  • 불변식 보장과 보안을 위해 사용

 

transient
  • 직렬화에서 제외할 필드를 표시하는 데 사용되는 키워드
  • 객체의 논리적 상태와 무관한 필드라고 확신할 때만 사용

 

synchronized
  • 객체 전체 상태를 읽는 메서드에 동기화 적용하기
  • 직렬화 시 일관성이 깨지지 않도록 하기 위함

 

serialVersionUID
  • 직렬화된 클래스의 버전을 관리하기 위한 고유 식별자
  • 호환성
    • serialVersionUID으로 버전을 관리
    • serialVersionUID이 일치하면 구조 변경이 있어도 역직렬화 가능
  • 효율적
    • 명시적으로 설정하면, 자동 생성 시 연산 비용을 아낄 수 있음

 

'Java' 카테고리의 다른 글

[Effective Java] 12-2. 직렬화  (2) 2025.01.03
[Effective Java] 11-2. 동기화  (2) 2025.01.03
[Effective Java] 11-1. 동기화  (0) 2025.01.01
[Effective Java] 10-2. 예외  (0) 2025.01.01
[Effective Java] 10-1. 예외  (0) 2025.01.01