백엔드 개발자라면 대답해야 할 100가지 질문

7. 자바 직렬화란 무엇일까요? 또 언제 사용할까요?

ignuy 2023. 7. 27.

자바를 다루면서, 더 정확히는 Springframework를 다루면서 객체에 대해 활용할 상황을 겪는다면 심심치 않게 직렬화(Serialize)에 대해서 듣게 된다. 대체 뭐길래 자주 듣는 것인가, 언제/어떻게 활용할 수 있는가 한번 알아보자.

직렬화(Serialize)

자바에서 직렬화란 자바 시스템 내부에서 사용되는 객체를 외부의 자바시스템에서 사용할 수 있도록 byte 형태로 전환하는 기술을 말한다. 거꾸로 자바 시스템이 외부에서 byte형태의 데이터를 받고 객체로 변환하는 기술을 역직렬화라고 한다.

Book book = new Book();
System.out.println(books[0]);

/**
 * com.example.test.BookTest$Book@3d00c2ae
 */

만들어낸 객체에 toString 오버라이딩이 안되어 있다면 출력했을 때 메모리에 올라가는 주소가 출력되어 나온다. JVM이 메모리 영역에 올라가 있는 객체를 그냥 외부로 전송하게 되면 객체의 데이터가 전송되는 것이 아니라 이 주소값이 전송되게 된다. 외부 시스템에서는 주소값을 받으면 원하는 주소에 접근할 수 없어 무용지물이다. 따라서, 외부에서 데이터를 읽을 수 있는 전처리가 필요하다. 이 과정이 직렬화이고 객체 내의 데이터를 바이트 형태로 변환하여 Serializable 인터페이스를 구현하는 것으로 표현된다.

직렬화 방법

Serializable Interface

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
...
}

___

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
...
}

자바 LinkedList의 선언 부분을 확인해보면 Serializable이라는 인터페이스가 implements 뒤에 오는 것이 보인다. 그 아래 있는 HashMap이나 ArrayList와 같은 자료구조뿐 아니라 String과 같은 타입들도 모두 이 인터페이스가 붙어있다.

package java.io;

public interface Serializable {
}

인터페이스를 자세히 보면 조금 황당하다. 아무런 메서드가 없는 빈 껍데기... 주석을 읽어보면 직렬화 가능의 의미 식별만을 수행하는 인터페이스라는 것을 알 수 있다(보통 이런 인터페이스를 마커 인터페이스라고 한다). JVM은 이 Serializable이 달려있는 클래스를 직렬화해야 한다는 사실을 이 마커 인터페이스를 통해서 알 수 있다.

ObjectOutputStream

java.io.ObjectOutputStream를 사용하여 직렬화를 진행한다.

Book book = new Book("21424", "Java Pro", "김하나");
byte[] serializedBook;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
    try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
        oos.writeObject(book);
        // serializedBook -> 직렬화된 Book 객체 
        serializedBook = baos.toByteArray();
    }
}
// 바이트 배열로 생성된 직렬화 데이터를 base64로 변환
System.out.println(Base64.getEncoder().encodeToString(serializedBook));

역직렬화 방법

직렬화 대상이 된 객체의 클래스가 클래스 패스에 존재해야 하며 import되어 있어야 한다. 또한, 자바 직렬화 대상 객체는 동일한 serialVersionUID를 가지고 있어야 한다(반드시 구현할 필요는 없다. 자바 직렬화 스펙에서는 SUID가 선언되어 있지 않으면 클래스의 기본 해쉬값을 SUID로 사용한다).

역직렬화의 구체적인 코드는 아래와 같다.

// 직렬화 예제에서 생성된 base64 데이터 
String base64Book = "...생략";
byte[] serializedBook = Base64.getDecoder().decode(base64Book);
try (ByteArrayInputStream bais = new ByteArrayInputStream(serializedBook)) {
    try (ObjectInputStream ois = new ObjectInputStream(bais)) {
        // 역직렬화된 Book 객체를 읽어온다.
        Object objectBook = ois.readObject();
        Book book = (Book) objectBook;
        System.out.println(book);
    }
}

 

JSON이랑 다른건가? 같은 건가?

JSON에서도 직렬화한다는 표현이 있다. JavaScript를 사용한다면 아래와 같은 코드를 자주 본 적 있을 것이다. 아래와 반대의 경우도 JSON.parse를 사용한다.

console.log(JSON.stringify({ x: 5, y: 6 }));
/**
 * "{"x":5,"y":6}"
 */

그럼 외부에서 JSON.sringify()를 이용해서 직렬화된 데이터는 자바 시스템 내부로 들어올 때 전처리 과정이 없어도 되는 것인가?

정답은 틀렸다.

자바에서 사용하는 Serialize는 자바 시스템 내에서 활용하기 위해 만들어진 기술이다. 그럼 자바 시스템 간에서는 CSV나 JSON, XML 등을 이용해서 데이터를 전송하지 않는 것인가는 물음을 다시 던진다면 그 또한 틀렸다.

자바 직렬화는 대부분의 기술이 그렇듯 장단점이 명확한 기술이다. 목적에 맞는 활용법에 따라 적절하게 사용해야 한다. 기회가 된다면 자바 직렬화의 장단점에 대해 상세히 기술한 테크 블로그를 정리하여 포스팅하겠다.

그럼 언제 자바 직렬화를 활용하는가?

JVM의 메모리에서만 상주되어 있는 개체 데이터를 그대로 영속화(Persistence)할 때 사용되는 경우가 많다. 시스템이 종료되어도 없어지지 않는 성격을 가져야 하는 데이터이기 때문에 네트워크로 전송하고 필요할 때 손쉽게 가져오기 위해 사용한다. 그 대표적인 예시로 Servlet Session, Cache, 자바 RMI(Remote Method Invocation)이 있다.

자세한 활용법에 대해서도 추후 정리하겠다.

 

댓글