프로그래밍 언어/[JAVA]

[Java] 직렬화(Serialization)와 역직렬화(Deserialization)란? transient 변수란?

연구소장 J 2023. 1. 26. 23:14

직렬화(Serializaion)란? 역직렬화(Deserialization)란?

직렬화는 객체를 저장 가능한 상태(예를 들어 디스크에 파일 형태 등) 혹은 전송 가능한 상태(네트워크 상의 데이터 스트림 형태)로 변환하는 것을 뜻한다 - 위키백과 -

직렬화와 역직렬화
[그림 1] 직렬화와 역직렬화 ( 출처 - 참고5 )

 

 

쉽게 말해 직렬화는 객체를 저장, 전송할 수 있는 특정 포맷 상태로 바꾸는 과정이라고 할 수 있다.

 

역직렬화는 말 그대로 직렬화의 반대이다. 즉, 특정 포맷 상태의 데이터를 다시 객체로 변환하는 것을 뜻한다. 

 

데이터 직렬화 포맷

  • CSV, XML, JSON 형태의 직렬화
    • 사람이 읽을 수 있다
    • 저장 공간의 효율성이 상대적으로 떨어지고, 파싱 하는 시간이 오래 걸린다
    • 주로 데이터의 양이 적을 때 사용한다
  • Binary 직렬화
    • 사람이 읽을 수 없다
    • 저장 공간의 효율성이 상대적으로 높고, 파싱 하는 시간이 빠르다
    • 주로 데이터의 양이 많을 때 사용한다
    • 모든 시스템에서 사용 가능하다
  • java 직렬화
    • java 시스템 간의 데이터 교환이 필요할 때 사용한다
    • Serializable 인터페이스를 구현함으로써 사용가능하다

 

Java에서 직렬화가 필요한 이유

일반적으로 Java에서의 직렬화는 객체를 Binary 형태로 변환하는 것을 뜻한다. disk에 객체를 저장하거나 컴퓨터 네트워크 상으로 객체를 전송하고 싶다면 Binary 형태로 바꿔야지만 가능하다. 객체 그 자체는 disk나 네트워크 장비가 이해할 수 없을 것이다. 

 

 

Java 직렬화와 transient 키워드

java.io.Serializable 인터페이스를 구현함으로써 Java 직렬화를 수행할 수 있다.

 

public class Student implements Serializable {
    public static final long serialVersionUID = 1234L;
 
    private long studentId;
    private String name;
    private transient int age;	// transient 변수는 직렬화에서 제외
 
    public Student(long studentId, String name, int age) {
        this.studentId = studentId;
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return String.format("%d - %s - %d", studentId, name, age);
    }

}

 

위와 같이 Student 클래스는 Serializable 인터페이스를 구현함으로써 Java 직렬화를 수행할 수 있다.

이를 통해 객체를 바이트 스트림으로 변환할 수 있고, 이를 디스크 파일에 저장하거나 네트워크 상으로 전달할 수 있다.

 

또한, transient 키워드를 사용하면 해당 변수는 직렬화에서 제외할 수 있다. 데이터를 저장하거나 전송할 때 민감한 정보 등을 제외하고 싶은 경우에 사용할 수 있다.

 

Student 클래스는 serialVersionUID라는 상수를 가지고 있다. 이 상수를 선언하지 않으면 내부적으로 클래스의 구조 정보를 이용하여 자동으로 생성된 해시 값이 할당된다. 

 

문제는 직렬화할 때와 역직렬화 할시에 serialVersionUID 값이 다르면 InvalidClassException 예외가 발생한다는 것이다.

만약 개발자가 Student 클래스를 직렬화하여 파일로 저장해 놓았다고 하자. 나중에 개발자가 Student 클래스를 변경하면 serialVersionUID 값이 달라지게 된다. 따라서 역직렬화 시 오류가 발생한다.

 

public static void main(String[] args){
    Student student = new Student(516L, "아이유", 31);
    byte[] serializedMember;
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
        try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(member);
            // serializedMember -> 직렬화된 Student 객체
            serializedMember = baos.toByteArray();
        }
    }
    // 바이트 배열로 생성된 직렬화 데이터를 base64로 변환
    System.out.println(Base64.getEncoder().encodeToString(serializedMember));
}

 

java.io.ObjectOutputSream를 사용하여 Student 클래스를 직렬화할 수 있다. 

 

public static void main(String[] args){
    // 직렬화 예제에서 생성된 base64 데이터
    String base64Member = "...생략";
    byte[] serializedMember = Base64.getDecoder().decode(base64Member);
    try (ByteArrayInputStream bais = new ByteArrayInputStream(serializedMember)) {
        try (ObjectInputStream ois = new ObjectInputStream(bais)) {
            // 역직렬화된 Student 객체를 읽어온다.
            Object objectMember = ois.readObject();
            Student student = (Student) objectMember;
            System.out.println(student);
        }
    }
}

 

java.io.ObjectInputStream을 사용하여 역직렬화할 수 있다.

 

 

Java 직렬화? JSON 직렬화?

자바는 Serializable 인터페이스를 구현하는 객체를 바이트 스트림으로 직렬화/역직렬화하는 기능을 제공한다. 하지만 이러한 자바 직렬화는 치명적인 보안 이슈, 엄격한 타입 체크, 객체 구조 변경의 어려움 등 단점이 많다.

 

이펙티브 자바의 저자 조슈아 블로크도 자바에서 제공하는 직렬화 기능을 사용하지 않을 것을 강력히 권장하며 대안으로 JSON 등의 포맷을 사용하는 것을 추천했다. 

 

 


참고

 

1. https://en.wikipedia.org/wiki/Serialization

2. https://stackoverflow.com/questions/2475448/what-is-the-need-of-serialization-of-objects-in-java

3. https://www.codejava.net/java-se/file-io/why-do-we-need-serialization-in-java

4. https://steady-coding.tistory.com/576

5. https://data-flair.training/blogs/serialization-and-deserialization-in-java

 

반응형