owned this note
owned this note
Published
Linked with GitHub
# 직렬화 & 역직렬화

개발을 하다보면 객체를 파일로 저장하거나 네트워크로 전송해야 하는 경우가 있다.
이때 객체를 저장하거나 전송하기 위해 객체를 `직렬화(Serialization)`하고, 다시 객체로 복원하기 위해 `역직렬화(Deserialization)`를 해야 한다.
직렬화는 객체의 상태를 저장하거나 네트워크를 통해 객체를 전송할 필요가 있을 때 필수적이다. 예를 들어, 분산 시스템에서 서버 간에 객체를 교환하거나, 어플리케이션의 상태를 영속화하기 위해 사용된다.
> Save & Load와 유사한 개념
## 직렬화(Serialization)
- 객체를 데이터 스트림으로 변환하는 것
- 객체를 파일, DB로 저장하거나 네트워크로 전송하기 위해 사용
## 역직렬화(Deserialization)
- 데이터 스트림을 객체로 변환하는 것
- 파일, DB, 네트워크로부터 객체를 읽거나 받아올 때 사용
## Java의 직렬화
자바에서는 객체의 직렬화를 직접 지원한다. 단, Java의 직렬화를 사용하려면 직렬화 대상 클래스가 `java.io.Serializable` 인터페이스를 구현해야 한다.
```java
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private transient int age; // transient 키워드로 직렬화 대상에서 제외
// 생성자, getter, setter 생략
}
```
- `Serializable` : 직렬화 대상 클래스에 구현해야 하는 인터페이스
- `serialVersionUID` : 직렬화된 객체의 버전을 식별하기 위한 ID
- `serialVersionUID`를 명시적으로 선언하지 않으면 JVM이 자동으로 생성한다.
- `transient` : 직렬화 대상에서 제외할 필드에 사용하는 키워드
### Java 직렬화 예
```java
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializationExample {
public static void main(String[] args) {
User user = new User();
user.setName("홍길동");
user.setAge(30);
try (FileOutputStream fileOut = new FileOutputStream("user.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
out.writeObject(user);
System.out.println("직렬화 완료");
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
- `User` 객체를 `user.ser` 파일로 직렬화하여 저장한다.
### Java 역직렬화 예
```java
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializationExample {
public static void main(String[] args) {
try (FileInputStream fileIn = new FileInputStream("user.ser");
ObjectInputStream in = new ObjectInputStream(fileIn)) {
User user = (User) in.readObject();
System.out.println("역직렬화 완료");
System.out.println("이름: " + user.getName());
// age 필드는 transient로 선언되어 직렬화에서 제외되므로 기본값인 0이 출력된다.
System.out.println("나이: " + user.getAge());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
- `user.ser` 파일로부터 객체를 역직렬화하여 복원한다.
- `age` 필드는 `transient`로 선언되어 직렬화에서 제외되었으므로, 기본값인 0이 출력된다.
### serialVersionUID
`serialVersionUID`는 직렬화된 객체의 버전을 식별하기 위한 ID이다. 직렬화된 객체를 역직렬화할 때, 객체의 클래스와 `serialVersionUID`가 일치하는지 확인한다.
`serialVersionUID`를 명시적으로 선언하지 않으면 JVM이 자동으로 생성한다. 즉, `serialVersionUID`는 필수적으로 선언하지 않아도 된다.

#### serialVersionUID 가 일치하지 않는 경우
`serialVersionUID`가 일치하지 않으면 역직렬화할 때 `InvalidClassException`이 발생한다.
```java
import java.io.*;
public class User implements Serializable {
private static final long serialVersionUID = 2L;
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
```
- `User` 클래스의 `serialVersionUID`를 2로 변경한다.
```java
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializationExample {
public static void main(String[] args) {
try (FileInputStream fileIn = new FileInputStream("user.ser");
ObjectInputStream in = new ObjectInputStream(fileIn)) {
User user = (User) in.readObject();
System.out.println("역직렬화 완료");
System.out.println("이름: " + user.getName());
System.out.println("나이: " + user.getAge());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
- `User` 클래스의 `serialVersionUID`를 2로 변경했지만, `user.ser` 파일이 이전 버전의 `User` 클래스로 직렬화된 파일(`serialVersionUID`=1L)이다. 따라서 `InvalidClassException`이 발생한다.
#### serialVersionUID를 명시적으로 선언하는 이유
`serialVersionUID`는 명시적으로 선언하지 않더라도 JVM이 자동으로 생성해주지만, 명시적으로 선언하는 것 권장된다.
클래스의 변경에 따라 `serialVersionUID`가 자동으로 변경될 수 있으므로, 명시적으로 선언하면 버전 관리에 용이하기 때문이다.
`serialVersionUID`는 `private static final long` 타입이어야 한다.
## 직렬화의 문제점
직렬화는 객체를 저장하거나 전송하기 위해 사용되지만, 항상 만능은 아니다. 직렬화에는 다음과 같은 문제점이 있다.
1. 보안 문제
- 직렬화된 데이터는 평문으로 저장되므로, 중요한 정보가 노출될 수 있다.
2. 역직렬화의 위험성
- 역직렬화 과정은 `ObjectInputStream`의 `readObject()` 메소드를 통해 이루어지는데, 이 과정에서 악의적인 코드를 실행하는 공격인 가젯(gadget) 공격에 노출될 수 있다.
3. 생성자 호출 우회 문제
- 직렬화된 데이터는 생성자를 호출하지 않고 객체를 생성하기 때문에, 객체 초기화에 필요한 작업이 누락될 수 있다.
4. 플랫폼 종속성 문제
- 자바 직렬화는 자바 플랫폼에 종속적이므로 다른 플랫폼에서 역직렬화할 때 문제가 발생할 수 있다.
5. 유지보수 문제
- 직렬화된 데이터는 객체의 내부 구조를 알 수 없기 때문에 유지보수가 어려울 수 있다.
6. 릴리즈 후 수정이 어렵다
- 직렬화된 데이터는 클래스의 구조가 변경되면 역직렬화할 때 오류가 발생할 수 있다. 따라서 기존 데이터를 읽을 수 없게 되는 문제가 발생할 수 있다.
7. 캡슐화를 깨트릴 수 있다
- 직렬화는 객체의 모든 필드를 직렬화하기 때문에, 캡슐화를 깨트릴 수 있다.
위와 같이 직렬화는 보안 문제와 유지보수 문제 등 여러 문제점을 가지고 있어, 도입 시에 많은 주의와 충분한 검토가 필요하다.
또한 이런한 직렬화 문제에 대응하기 위해, `JSON`, `XML`, `Protocol Buffers` 등의 대안이 제시되고 있다.