개요
오늘은 자바에서 밥 먹듯이 사용되는 직렬화에 대해 정리해 보자.
자바 직렬화
자바에서 직렬화란 자바 환경 내부에서 사용되던 객체나 데이터를 외부의 자바 시스템에서도 사용 가능하도록 바이트 형태로 변환하는 과정을 의미한다.
조금 더 전공자처럼 얘기하면 JVM의 런타임 데이터 영역에 위치하고 있는 객체를 바이트 형태로 변환하는 과정이다.
역직렬화는 이것과 반대로 바이트 형태로 변환된 데이터를 다시 자바에서 사용할 수 있는 객체 형태로 변환한 후, JVM의 런타임 데이터 영역에 저장하는 것을 의미한다.
Serializable
Serializable의 사전적 의미는 직렬화이다.
직렬화를 자바에서 구현하기 위해선 일단 Serializable 인터페이스를 상속받은 후 구현해야 한다.
class Member implements Serializable {
private String name;
private int age;
private String phoneNumber;
public Member(String name, int age, String phoneNumber) {
this.name = name;
this.age = age;
this.phoneNumber = phoneNumber;
}
}
public class MemberTest {
public static void main(String[] args) throws Exception {
Member member = new Member("홍길동", 28, "010-1234-1234");
byte[] serializedMember;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(member);
serializedMember = baos.toByteArray(); // member를 직렬화
}
}
}
위의 과정을 거치면 ObjectOutputStream을 통해 Member 객체를 직렬화할 수 있다.
역직렬화
역직렬화는 직렬화보다 조금 더 까다롭다.
조건은 다음과 같다.
- 직렬화 객체는 동일한 serialVersionID을 가지고 있어야 한다.
- 직렬화된 객체의 클래스에 대한 정보가 클래스패스에 포함되어 있어야 한다.
- JVM이 클래스 파일을 찾을 수 있어야 함
public class MemberTest {
public static void main(String[] args) throws Exception {
Member member = new Member("홍길동", 28, "010-1234-1234");
byte[] serializedMember;
try (ByteArrayOutputStream output = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(output)) {
oos.writeObject(member);
serializedMember = output.toByteArray();
}
Member deserializedMember;
try (ByteArrayInputStream input = new ByteArrayInputStream(serializedMember);
ObjectInputStream ois = new ObjectInputStream(input)) {
deserializedMember = (Member) ois.readObject();
}
System.out.println(deserializedMember.toString());
}
}
[결과]
Member{name='홍길동', age=28, phoneNumber='010-1234-1234'}
결과를 보다시피 바이트 형태의 Member 데이터를 다시 ObjectInputStream을 사용해 자바 환경(JVM)에서 사용할 수 있는 객체 형태로 변환되었다.
serialVersionID
역직렬화 조건은 JVM이 직렬화된 객체의 클래스 정보를 알아야 하고, serialVersionID(SUID)가 필요하다.
이런 조건이 있는 이유는 더 안전한 역직렬화를 위해서이다.
아래는 역직렬화의 실패 예시이다.
- Member 객체 직렬화 → 바이트 생성
- Member 객체에 nickname 멤버 변수 추가
- 직렬화 결과로 생성된 바이트를 다시 역직렬화에 사용
- 에러 발생 (InvalidClassException)
에러가 발생한 이유는 직렬화를 진행할 때의 Member와 역직렬화를 할 때 Member가 다르기 때문이다.
그렇기 때문에 자바에선 SUID을 따로 객체 내부에 설정한다.
class Member implements Serializable {
private static final long serialVersionID = 랜덤한 값;
private String name;
private int age;
private String phoneNumber;
...
}
저 SUID을 토대로 객체가 생성될 당시의 클래스 버전을 정의하는 것이다.
만약 SUID가 다른 바이트 데이터를 역직렬화에 사용할 경우 현재 역직렬화될 클래스의 SUID을 확인한 후, 일치해야 역직렬화가 정상적으로 진행되는 것이다.
하지만 우린 개발할 때 SUID을 따로 명시하진 않는다.
왜냐하면 Intellij나 이클립스 같은 IDE에서 자동으로 생성해 주고, IDE을 사용할 수 없는 경우엔 자바 컴파일러가 SUID을 대신 생성해 주기 때문이다.
'개발 일기' 카테고리의 다른 글
[개발 일기] 2025.01.25 - volatile (1) | 2025.01.25 |
---|---|
[개발 일기] 2025.01.24 - 낙관적 락 vs 비관적 락 (0) | 2025.01.24 |
[개발 일기] 2025.01.22 - static (0) | 2025.01.22 |
[개발 일기] 2025.01.21 - TCP/IP (0) | 2025.01.21 |
[개발 일기] 2025.01.20 - 함수형 인터페이스 (0) | 2025.01.20 |