본문 바로가기
Tech/Java

Java 직렬화(Serializable)란?

반응형
CS 기본 지식 공부 과정입니다.
우아한 형제들 기술 블로그에 내용이 잘 정리되어있어 참고했습니다

https://techblog.woowahan.com/2550/

 

자바 직렬화, 그것이 알고싶다. 훑어보기편 | 우아한형제들 기술블로그

{{item.name}} 자바의 직렬화 기술에 대한 대한 이야기입니다. 간단한 질문과 답변 형태로 자바 직렬화에 대한 간단한 설명과 직접 프로젝트를 진행하면서 겪은 경험에 대해 이야기해보려 합니다.

techblog.woowahan.com


1. 직렬화란?

  • 자바 내부에서 사용되는 객체 혹은 데이터를 외부 자바 시스템에서도 사용할 수 있게 byte 형태의 데이터로 변환하는 기술입니다
  • 반대로 byte 형태로 변환된 데이터를 자바 객체, 데이터로 다시 변환하는 것을 역직렬화라고 한다 
  • 필요성
    • 자바 시스템 간의 데이터 교환을 위해 사용한다 ( 시스템 독립적이기 위해서는 CSV, JSON, ProtoBuffer 등등 사용 )
    • 직렬화의 기본 조건만 지키면 별도의 큰 코딩없이(java.io.Serializable만 구현해주면) 쉽게 데이터 교환이 가능하다

 

2. 직렬화 방법

    • 직렬화 조건
      • 자바 기본형 타입 ( primitive type ), Wrapping 클래스도 가능합니다
      • java.io.Serializable 구현한 클래스
    • Serializable 구현 클래스 예제
      
      // MyObject
      import java.io.Serializable;
      import java.time.LocalDateTime;
      import java.util.StringJoiner;
      
      public class MyObject implements Serializable {
      
          private Long id;
          private String name;
          private int age;
          private LocalDateTime registerTime;
      
          public MyObject(long id, String name, int age, LocalDateTime registerTime) {
              this.id = id;
              this.name = name;
              this.age = age;
              this.registerTime = registerTime;
          }
      
          public long getId() {
              return id;
          }
      
          public String getName() {
              return name;
          }
      
          public int getAge() {
              return age;
          }
      
          public LocalDateTime getRegisterTime() {
              return registerTime;
          }
      
          @Override
          public String toString() {
              return new StringJoiner(", ", MyObject.class.getSimpleName() + "[", "]")
                  .add("id=" + id)
                  .add("name='" + name + "'")
                  .add("age=" + age)
                  .add("registerTime=" + registerTime)
                  .toString();
          }
      }
    • 직렬화 예제
      • ByteArrayOutputStream과 ObjectOutputStream을 이용하여 "MyObject"를 직렬화할 수 있습니다
      • Serializable이 구현되지 않은 객체를 직렬화하면 Exception이 발생합니다
        
        // Exception
        java.io.NotSerializableException: com.my.toyproject.serializable.MyObject
      • 테스트 코드 예제
        // JUnit5 test
        @Test
        void serializableTest() throws IOException {
            MyObject myObject = new MyObject(1L, "주지민", 30, LocalDateTime.now());
        
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(myObject);
            byte[] results = baos.toByteArray();
        
            System.out.println(Base64.encodeBase64String(results));
        }
      • 추가: Base64 인코딩 필요성
        • Base64란 8비트 이진 데이터(예를 들어 실행 파일이나, ZIP 파일 등)를 문자 코드에 영향을 받지 않는 공통 ASCII 영역의 문자들로만 이루어진 일련의 문자열로 바꾸는 인코딩 방식을 가리키는 개념
        • Base64를 사용하게 되면 6bit당 2bit의 Overhead가 발생하여 전송해야 될 데이터의 크기가 약 33% 정도 늘어나고, 인코딩과 디코딩의 추가 연산까지도 필요하게 되는데 굳이 사용하는 이유는?
        • 문자를 위한 byte 코드를 전송해야할 때, 공통된 포맷으로 변경하여 시스템 독립적으로 동일하게 전송 또는 저장되는 걸 보장하기 위해 사용하여 아래와 같은 문제점을 해결
          • ASCII는 7 bits Encoding인데 나머지 1bit를 처리하는 방식이 시스템 별로 상이합니다.
          • 일부 제어 문자의 경우 시스템 별로 다른 코드값을 가진다

 

3. 역직렬화 방법

  • 위에서 설명했듯이 byte 형태로 변환된 데이터를 자바 객체, 데이터로 다시 변환하는 것을 역직렬화라고 합니다
  • 역직렬화 예제
    • ByteArrayInputStream과 ObjectInputStream을 이용하여 역직렬화를 할 수 있습니다
    • 테스트 코드 예제
      // Deserializable
      byte[] bytes = Base64.decodeBase64(encodedBase64);
      ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
      ObjectInputStream ois = new ObjectInputStream(bais);
      Object deserializableObject = ois.readObject();
      MyObject deserializableMyObject = (MyObject) deserializableObject;
      
      System.out.println(deserializableMyObject);

 

4. 유의사항

  • serialVersionUID 관리
    • 자바 직렬화 스펙 정의
      • SUID(serialVersionUID) 필수 값은 아니다.
      • 호환 가능한 클래스는 SUID값이 고정되어 있다.
      • SUID가 선언되어 있지 않으면 클래스의 기본 해쉬값을 사용한다
    • 직접 기술하지 않아도 내부 적으로 serialVersionUID 정보가 추가되며, 자동으로 생성된 클래스의 해쉬 값이 serialVersionUID로 사용된다는 것을 확인 할 수 있습니다
    • 따라서 serialVersionUID를 기술하지 않고 객체의 스펙을 변경하게되면 serialVersionUID도 같이 변경이 되고, 기존 스펙으로 직렬화를 했던 데이터를 역직렬화 요청시 java.io.InvalidClassException이 발생하게 됩니다
    • serialVersionUID의 값이 동일하면 멤버 변수 및 메서드 추가는 크게 문제가 발생하지 않습니다
    • 멤버 변수 제거 및 이름 변경 또한 오류는 발생하지 않지만 데이터는 누락됩니다
    • 결론
      • 외부(DB, 캐시 서버, NoSQL 서버 등)에 장기간 저장될 정보는 자바 직렬화 사용을 지양
      • 역직렬화 대상의 클래스가 언제 변경이 일어날지 모르는 환경에서 긴 시간 동안 외부에 존재했던 직렬화된 데이터는 쓰레기가 될 가능성이 높습니다
      • 자주 변경되는 클래스는 사용을 자제하자
728x90
반응형