Back-end/Java Language

직렬화(Serialization)

prden 2022. 10. 12. 20:09

1. 의미

객체를 컴퓨터에 저장했다가 다음에 다시 꺼내 쓸 때 혹은 네트워크를 통해 컴퓨터 간에 서로 객체를 주고받을 때 직렬화를 통해 가능하다.(직렬화 한 후에 저장하거나 전송하고 필요할 때 역직렬화해서 꺼내 쓴다.)

직렬화란 객체를 데이터 스트림(Byte형태로 변환)으로 만드는 것을 의미한다. 다시 말해 객체에 저장된 데이터를 스트림에 쓰기(write) 위해 연속적인(serial) 데이터로 변환하는 것을 말한다. 반대로 스트림으로부터 데이터를 읽어서 객체를 만드는 것을 역직렬화(deserialization)라고 한다.

 

◇객체란? 클래스에 정의된 인스턴스 변수의 집합(클래스 변수나 클래스 메서드가 포함되지 않음)을 의미한다.

 따라서 객체를 저장한다고 하는 것은 객체의 모든 인스턴스 변수의 값을 저장한다는 의미이다. 객체를 직렬화, 역직렬화 할 때는 ObjectInputStream과 ObjectOutputStream을 사용해야 한다. 

두 객체가 동일한지의 판단기준은 두객체의 인스턴스 변수의 값이 같은지 다른지 여부이다. 

 

2. 직렬화 조건

1)Java.io.Serializable 인터페이스를 상속받은 객체와 Primitive 타입의 데이터가 직렬화의 대상이 될 수 있다.

  • 기본자료형(Primitive Type)은 정해진 Byte의 변수이기 때문에 Byte 단위로 변환하는 것에 문제가 없지만,
  • 객체의 크기는 가변적이며, 객체를 구성하는 자료형들의 종류와 수에 따라 객체의 크기가 다양하게 바뀔 수 있기 때문에 객체를 직렬화하기 위해 Serializable 인터페이스를 구현해야 한다.

2) 객체의 멤버들 중 Serializable 인터페이스가 구현되지 않은 것이 존재하면 안된다.

3)Transient가 선언된 멤버는 전송되지 않는다.

  • 객체 내에 Serializable 인터페이스가 구현되지 않은 멤버 때문에 NonSerializableException이 발생하는 경우, Transient를 선언해주면 직렬화 대상에서 제외되기 때문에 문제없이 해당 객체를 직렬화 할 수 있다.

 

3.  ObjectInputStream(저장)과 ObjectOutputStream(읽기)

1) 파일에 객체를 저장(직렬화) 하고싶으면

FileOutputStream fos = new FileOutputStream("objectfile.ser");

ObjectOutputStream out = new ObjectOutputStream(fos);

out.writeObject(new UserInfo());

//위의 코드는 objectfile.ser이라는 파일에 Userinfo객체를 직렬화하여 저장하는 것

 

2) 역직렬화는

FileInputStream fis = new FileInputStream("objectfile.ser");

ObjectInputStream in = new ObjectInputStream(fis);

UserInfo info = (UserInfo) in.readObject();

//objectfile.ser 에 있는 UserInfo를 역직렬화 해서 읽어오는 것

 

3) 직렬화 작업시간을 단축하기 위해 2개 메서드 직접 구현해야 한다. 

private void writeObject (objectOutputStream out)throw IOException { 

    out.writeUTF(name);

    out.writeUTF(password); 

    out.defaultWriteObject();     

}

private void readObject(objectInputStream in) throw IOException, ClassNotFoundException {

   name = in.readUTF();

   password = in.readUTF();

   in.defaultReadObject();       

}

 

4. 직렬화 가능한 클래스 만들기 -Serializable, transient

public class UserInfo implements Serializable {

  String name;

  String password;

  int age;

//. Object obj = newObject(); // Object 객체는 직렬화 할 수 없다. 

// Object obj = new String("abc"); String은 직렬화될 수 있다. 

 transient Object obj = newObject(); // transient를 붙이면 직렬화 대상에서 제외된다. 

}

 

5. 직렬화 할 경우 주의 사항 및 사용되는 상황

1) 직렬화 한 후에 역직렬화해서 읽어낼 때 직렬화할 때의 순서와 일치해야 한다. 따라서 직렬화할 객체가 많을 때는 각 객체를 개별적으로 직렬화 하는 것보다 ArrayList와 같은 컬렉션에 저장해서 직렬화하는 것이 좋다. 역직렬화할 대 ArrayList 하나만 역직렬화 하면 되므로, 역직렬화할 객체의 순서를 고려하지 않아도 되기 때문이다.

 

2) JVM의 메모리에서 상주하는 객체 데이터를 그대로 영속화(Persistence)할 때 사용된다.

  •  시스템이 종료되더라도 사라지지 않으며, 영속화된 데이터이기 때문에 네트워크로 전송도 가능하다.

3) Servlet Session

  • Servlet 기반의 WAS들은 대부분 세션의 Java 직렬화를 지원한다.
  • 파일로 저장, 세션 클러스터링, DB를 저장하는 옵션 등을 선택하면 세션 자체가 직렬화되어 저장 및 전달된다.

4) Cache

  • 캐시할 부분을 직렬화된 데이터를 저장해서 사용

5) Java RMI(Remote Method Invocation)

  • 원격 시스템의 메서드를 호출할 때 전달하는 메세지(객체)를 직렬화하여 사용
  • 메세지(객체)를 전달받은 원격 시스템에서는 메세지(객체)를 역직렬화하여 사용

6) 객체가 세션에 저장하지 않는 단순한 데이터 집합이고, 컨트롤러에서 생성되어서 뷰에서 소멸하는 데이터의 전달체라면 객체 직렬화는 고려하지 않아도 된다.

 

7) 세션 관리를 스토리지나 네트워크 자원을 사용한다면 객체 직렬화를 해야 하고, 메모리에서만 관리한다면 객체 직렬화를 할 필요가 없다. 둘 다 고려한다면 직렬화가 필요하다.

 

6. serialVersionID를 직접 관리하는 이유?

  1. Java 직렬화 대상 객체는 동일한 serialVersionUID를 가지고 있어야 한다.
  2. 그런데 serialVersionUID를 직접 선언하지 않아도, 내부적으로 클래스의 구조 정보를 이용해 자동으로 생성된 해쉬값이 할당된다.
  3. 때문에 클래스의 멤버 변수가 추가되거나 삭제되면 serialVersionUID가 달라지는데,
  4. 역직렬화 할 때 기존의 serialVersionUID와 변경된 serialVersionUID가 다르면 java.io.InvalidClassException 예외가 발생한다.
  5. 그러므로 위의 코드(private static final long serialVersionUID = 1L;) 처럼 직접 serialVersionUID 값을 관리해야 클래스가 변경되어도 문제없이 직렬화/역직렬화를 할 수 있다.
  6. 하지만 serialVersionUID가 같다고 무조건 문제없이 직렬화/역직렬화 할 수 있는 것은 아니다.
    • 클래스의 멤버 변수 타입이 다르면 타입 예외가 발생한다.
    • 멤버 변수를 제거하거나 변수명을 바꾸면 예외는 발생하지 않지만 데이터는 누락된다.

7. 실무에서는

  1. serialVersionUID는 개발 시 직접 관리
  2. 역직렬화 대상 클래스의 멤버 변수 타입변경 지양
  3. 외부(DB, 캐시 서버, NoSQL 서버 등)에 장기간 저장될 정보는 Java 직렬화 사용 지양 (클래스 변경을 예측할 수 없으므로)
  4. 개발자가 직접 컨트롤할 수 없는 클래스(프레임워크 또는 라이브러리에서 제공하는 클래스)는 직렬화 지양
  5. 자주 변경되는 클래스는 Java 직렬화를 사용하지 않는 것이 좋다.
  6. 역직렬화에 실패하는 상황에 대한 예외처리 필수
  7. 직렬화된 데이터는 타입 정보등의 클래스 메타정보를 포함하기 때문에 JSON 포맷에 비해 약 2~10배 더 사이즈가 크다. 특히 직렬화된 데이터를 메모리 서버(Redis, Memcached)에 저장하는 환경에서 트래픽에 따라 네트워크 비용과 캐시 서버 비용이 급증할 수 있으므로, JSON 포맷으로의 변경을 고려해야 한다.

 

※Reference

1) 자바의 정석

2) https://github.com/Integerous/TIL/blob/c63a4bd09810317591a36b2dfe4f0d2a9e212dc1/Java/Serialize.md

 

'Back-end > Java Language' 카테고리의 다른 글

Exception Handling  (0) 2023.01.01
super()와 super  (0) 2022.12.11
for 문 자세히 알기  (0) 2022.03.20
Java heap, stack  (0) 2022.03.18
ThreadLocal  (0) 2022.03.18