직렬화(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는 원격 시스템 간의 메시지 교환을 위해 사용하는 자바 기술이다. 원격 메소드 호출 시 객체를 직렬화하여 네트워크를 통해 전송하며, 원격 시스템에서 객체를 역직렬화하여 사용할 수 있다.
자바 직렬화의 장점
- 직렬화는 자바의 고유 기술인 만큼 당연히 자바 시스템에서 개발에 최적화되어 있다.
- 자바의 광활한 레퍼런스 타입에 대해 제약 없이 외부에 내보낼 수 있다는 것이다.
자바 직렬화의 단점
- 직렬화는 용량이 크다
- 직렬화된 데이터는 일반적으로 JSON과 같은 다른 데이터 포맷에 비해 더 많은 용량을 차지한다. 이는 직렬화된 형태가 객체의 메타데이터와 추가 정보를 포함하기 때문이다.
- 역직렬화는 위험하다
- 역직렬화 과정에서 악의적인 데이터가 주입될 수 있다. 이로 인해 보안 취약점이 발생할 수 있으며, 이를 방지하기 위해서는 입력 데이터의 검증과 추가적인 보안 조치가 필요하다.
- 릴리즈 후에 수정이 어렵다
- 직렬화된 데이터는 클래스의 구조에 의존하므로, 클래스가 변경되면 직렬화된 데이터와 호환되지 않을 수 있다. 이로 인해 이전 버전의 데이터와 호환성 문제를 겪을 수 있다.
- 클래스 캡슐화가 깨진다
- 직렬화된 객체는 클래스의 내부 상태를 외부에 노출시킬 수 있다. 이는 클래스의 캡슐화 원칙을 위반할 수 있으며, 객체의 상태가 외부에서 직접 접근 가능하게 된다.
- 내부 클래스는 직렬화를 구현하면 안된다
- 내부 클래스(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
- 객체를 스트림에 직렬화할 때 사용된다.
- 객체의 인스턴스 변수 값만 직렬화하며,
static
변수나 메소드는 직렬화되지 않는다.
ObjectInputStream
- 스트림으로부터 객체를 역직렬화할 때 사용된다.
- 역직렬화 시, 직렬화 대상이 된 객체의 클래스가 클래스 경로(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
답글 남기기