serialize? Deserialize?

직렬화(serialize)란?

자바 시스템 내부에서 사용되는 객체나 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 변환하는 기술이다. 이렇게 변환된 데이터는 파일로 저장하거나 네트워크로 전송하여 다른 시스템에서도 사용할 수 있게 된다.

반대 되는 개념인 역직렬화(Deserialize)란?

직렬화된 데이터를 다시 객체로 복원하는 과정이다.

직렬화된 데이터를 다시 읽어서, 원래의 객체 상태로 돌려놓는 것이다.

직렬화의 종류

직렬화의 종류는 다양한데 CSV, JSON, 자바 직렬화 등이 있다

CSV

  • CSV는 데이터를 쉼표(,)로 구분하여 저장하는 형식이다.
  • 주로 간단한 데이터 교환에 사용된다.
  • 예시: 이름, 나이, 직업\\n홍길동, 30, 개발자

JSON

  • JSON은 데이터를 키-값 쌍으로 표현하는 경량 데이터 교환 형식이다.
  • 인간이 읽기 쉽고 기계가 분석하고 생성하기 쉬운 장점이 있어, 웹에서 주로 사용된다.
  • 예시: {"이름": "홍길동", "나이": 30, "직업": "개발자"}

자바 직렬화

  • 자바 직렬화는 자바에서 기본적으로 제공하는 직렬화 기법으로, 객체를 바이트 스트림으로 변환하여 저장하거나 전송할 수 있다.
  • 자바 객체의 상태를 그대로 유지한 채로 전송하거나 영속화할 수 있는 장점이 있다.

JSON을 사용하면 되지 자바 직렬화를 왜 사용할까?

최근에는 JSON을 사용하는 추세가 많아지고 있다. JSON은 언어와 플랫폼에 독립적이며, 가독성이 뛰어나고 경량이기 때문에 웹 애플리케이션이나 API 통신에서 많이 사용된다.

하지만, 자바 직렬화는 특정한 경우에 여전히 유용하다.

자바 직렬화를 사용하는 주요 이유 중 하나는 JVM의 메모리에서만 상주하는 객체 데이터를 그대로 영속화(Persistence) 해야 할 때이다.

예를 들어, 자바 객체를 파일 시스템에 그대로 저장하거나 네트워크를 통해 다른 자바 애플리케이션으로 전송할 때, 자바 직렬화는 객체의 모든 필드를 그대로 보존할 수 있다는 큰 장점이 있다.

또한, 자바 직렬화는 객체의 상태를 완벽하게 유지할 수 있어서, 복잡한 객체 그래프를 직렬화하거나, 복잡한 객체 구조를 그대로 유지해야 하는 경우에 적합하다.

결론적으로, JSON과 자바 직렬화 중 어느 것을 사용할지는 프로젝트의 목적과 요구사항에 따라 달라진다. 웹과 같은 환경에서는 JSON이 더 나을 수 있지만, 자바 시스템 내부에서 객체의 상태를 그대로 유지하고 영속화해야 하는 경우에는 자바 직렬화가 더 적합할 수 있다.

자바 직렬화가 사용되는 곳

서블릿 세션 (Servlet Session)

  • 서블릿에서 세션을 메모리 위에서 운용할 때는 직렬화가 필요 없지만, 세션을 파일로 저장하거나 클러스터링을 통해 분산 시스템에서 사용할 때는 세션이 직렬화되어 저장되거나 전송된다.

캐시 (Cache)

  • Ehcache, Redis, Memcached와 같은 캐시 시스템에서는 직렬화를 통해 객체를 저장하거나 전송한다. 이들은 직렬화된 데이터를 메모리나 디스크에 저장하여 빠른 데이터 접근을 가능하게 한다.

자바 RMI (Remote Method Invocation)

  • 자바 RMI는 원격 시스템 간의 메시지 교환을 위해 사용하는 자바 기술이다. 원격 메소드 호출 시 객체를 직렬화하여 네트워크를 통해 전송하며, 원격 시스템에서 객체를 역직렬화하여 사용할 수 있다.

자바 직렬화의 장점

  1. 직렬화는 자바의 고유 기술인 만큼 당연히 자바 시스템에서 개발에 최적화되어 있다.
  2. 자바의 광활한 레퍼런스 타입에 대해 제약 없이 외부에 내보낼 수 있다는 것이다.

자바 직렬화의 단점

  1. 직렬화는 용량이 크다
    • 직렬화된 데이터는 일반적으로 JSON과 같은 다른 데이터 포맷에 비해 더 많은 용량을 차지한다. 이는 직렬화된 형태가 객체의 메타데이터와 추가 정보를 포함하기 때문이다.
  2. 역직렬화는 위험하다
    • 역직렬화 과정에서 악의적인 데이터가 주입될 수 있다. 이로 인해 보안 취약점이 발생할 수 있으며, 이를 방지하기 위해서는 입력 데이터의 검증과 추가적인 보안 조치가 필요하다.
  3. 릴리즈 후에 수정이 어렵다
    • 직렬화된 데이터는 클래스의 구조에 의존하므로, 클래스가 변경되면 직렬화된 데이터와 호환되지 않을 수 있다. 이로 인해 이전 버전의 데이터와 호환성 문제를 겪을 수 있다.
  4. 클래스 캡슐화가 깨진다
    • 직렬화된 객체는 클래스의 내부 상태를 외부에 노출시킬 수 있다. 이는 클래스의 캡슐화 원칙을 위반할 수 있으며, 객체의 상태가 외부에서 직접 접근 가능하게 된다.
  5. 내부 클래스는 직렬화를 구현하면 안된다
    • 내부 클래스(inner class)의 직렬화 형태는 불분명하므로 Serializable을 구현하면 안된다.

자바 직렬화 사용법

Serializable 인터페이스

자바에서 객체를 직렬화하려면 java.io.Serializable 인터페이스를 구현해야 한다. 이 인터페이스는 실제로 아무 메소드도 정의되어 있지 않은 마커 인터페이스이다. 직렬화하려는 클래스가 이 인터페이스를 구현함으로써, 자바의 직렬화 메커니즘에 의해 직렬화가 가능하다는 것을 표시한다. 만약 Serializable을 구현하지 않으면, 직렬화 중에 NotSerializableException 런타임 예외가 발생한다.

마커 인터페이스는 메소드 선언 없이, 클래스가 특정 속성이나 기능을 가짐을 표시하는 빈 껍데기 인터페이스를 말한다.
자바에서 마커 인터페이스의 대표적인 예로는 Serializable, Cloneable, Remote 등이 있다.

직렬화 요소 제외

자바에서 객체의 직렬화에서 특정 필드를 제외하고 싶다면 transient 키워드를 사용할 수 있다. transient 키워드가 붙은 필드는 직렬화 대상에서 제외되며, 직렬화 시에는 해당 필드가 기본값으로 설정된다.

import java.io.*;

class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    
    String name;
    transient int age; // 직렬화에서 제외될 필드
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class TransientExample {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 직렬화
        Person person = new Person("홍길동", 30);
        FileOutputStream fileOut = new FileOutputStream("person.ser");
        ObjectOutputStream out = new ObjectOutputStream(fileOut);
        out.writeObject(person);
        out.close();
        fileOut.close();
        
        // 역직렬화
        FileInputStream fileIn = new FileInputStream("person.ser");
        ObjectInputStream in = new ObjectInputStream(fileIn);
        Person deserializedPerson = (Person) in.readObject();
        in.close();
        fileIn.close();
        
        System.out.println("이름: " + deserializedPerson.name); // 홍길동
        System.out.println("나이: " + deserializedPerson.age); // 0 (기본값)
    }
}

ObjectOutputStream

  1. 객체를 스트림에 직렬화할 때 사용된다.
  2. 객체의 인스턴스 변수 값만 직렬화하며, static 변수나 메소드는 직렬화되지 않는다.

ObjectInputStream

  1. 스트림으로부터 객체를 역직렬화할 때 사용된다.
  2. 역직렬화 시, 직렬화 대상이 된 객체의 클래스가 클래스 경로(Class Path)에 존재해야 하고, 해당 클래스가 import되어 있어야 한다.

serialVersionUID

serialVersionUID는 직렬화된 객체의 버전 관리를 위한 고유 식별자이다.

직렬화된 객체를 역직렬화할 때, serialVersionUID가 클래스의 현재 버전과 일치해야 한다.

일치하지 않으면 InvalidClassException이 발생한다.

SerialVersionUID를 명시적으로 정의함으로써, 클래스의 구조가 변경되었을 때도 호환성을 관리할 수 있다.

import java.io.*;

class Person implements Serializable {
    private static final long serialVersionUID = 1L; 
// 명시적인 serialVersionUID

    String name;
    int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
`

serialVersionUID를 명시적으로 정의하지 않으면, 자바는 클래스의 구조에 기반하여 자동으로 생성하지만, 이는 클래스의 구조 변경 시 충돌을 일으킬 수 있다.

따라서 안정적인 직렬화와 역직렬화를 위해 serialVersionUID를 명시적으로 선언하는 것이 좋다.

참고자료

https://techblog.woowahan.com/2550

https://techblog.woowahan.com/2551

https://medium.com/@lunay0ung/basics-직렬화-serialization-란-feat-java-2f3eb11e9a8

https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%A7%81%EB%A0%AC%ED%99%94Serializable-%EC%99%84%EB%B2%BD-%EB%A7%88%EC%8A%A4%ED%84%B0%ED%95%98%EA%B8%B0


답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

Captcha loading…