직렬화와 역직렬화
직렬화(Serialization)란?
직렬화란 Java객체를 바이트 스트림으로 변환하여 파일이나 네트워크를 통해 전송할 수 있도록 하는 과정. 객체를 저장하거나 다른 시스템으로 전송할 때 필요하다.
역직렬화(Deserialization)란?
역직렬화란 직렬화된 바이트 스트림을 다시 객체로 변환하는 과정이다. 즉, 저장된 데이터를 복원하는 역할을 한다.
직렬화가 왜 필요할까?
데이터의 메모리 구조는 크게 2가지로 나뉜다 -> 값 형식 데이터(int, float, char 등등), 참조 형식 데이터(객체와 같은 참조 형식 변수)
이 두 가지 데이터 중에서 디스크에 저장하거나 통신할 때는 값 형식의 데이터만 사용할 수 있다. 참조 형식 데이터는 실제 데이터 값이 아닌 힙에 할당되어 있는 메모리 번지 주소를 가지고 있기 때문이다.
참조 형식 데이터를 사용할 수 없는 이유
예를 들어, 객체 A를 만들고 주소값이 A-1이라고 가정했을때, 이 값을 파일에 포함하여 저장하였다고 하면, 이후 프로그램을 종료하고 다시 실행해서 주소값 A-1을 가져오더라도 기존 A 객체의 데이터를 가져올 수 없다. 프로그램이 종료되면 기존 할당된 메모리는 해제되고 없어지기 때문이다.
네트워크 통신 또한 마찬가지이다. 각 PC마다 사용하고 있는 메모리 공간 주소는 전혀 다르기 때문에 내가 다른 PC로 전송한 A객체 데이터는 무의미하다. 이 데이터를 받은 PC의 메모리 주소에는 전혀 다른 값이 존재하기 때문이다.
그래서 직렬화가 왜 필요할까?
디스크에 저장하거나 통신할 때 값 형식 데이터만 가능하고, 참조 형식의 데이터는 안 된다는 것을 이해하였다. 그러면 직렬화는 도대체 왜 필요한 것일까?
직렬화를 하게 되면 각 주소 값이 가지는 데이터를 전부 끌어 모아서 값 형식 데이터로 변환해 준다. 직렬화가 된 데이터는 언어에 따라서 텍스트 또는 바이너리 등의 형태가 되는데, 이러한 형태가 되었을 때 저장하거나 통신할 때 파싱이 가능한 유의미한 데이터가 된다.
즉, 직렬화를 하는 이유는 사용하고 있는 데이터를 파일 저장 혹은 데이터 통신에서 파싱할 수 있는 유의미한 데이터를 만들기 위함이다.
데이터 직렬화의 종류
- CSV, XML, JSON 직렬화
- 사람이 읽을 수 있는 형태
- 저장 공간의 효율성이 떨어지고, 파싱하는 시간이 오래 걸린다.
- 데이터의 양이 적을 때 주로 사용
- 최근에는 JSON형태를 통해 데이터를 직렬화를 많이 한다
- 모든 시스템에서 사용이 가능하다.
- Binary직렬화
- 사람이 읽을 수 없는 형태
- 저장 공간을 효율적으로 사용할 수 있고, 파싱하는 시간이 빠르다
- 데이터의 양이 많을 때 주로 사용한다
- 모든 시스템에서 사용 가능하다
- Java 직렬화
- Java 시스템 간의 데이터 교환이 필요할 때 사용한다.
Java의 직렬화와 역직렬화
- Java직렬화
- 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에서 사용할 수 있도록 바이트 형태로 변환하는 기술
- JVM의 메모리에 상주되어 있는 객체 데이터를 바이트 형태로 변환하는 기술이다.
- Java역직렬화
- 바이트로 변환된 데이터를 다시 객체로 변환하는 기술이다.
- 직렬화된 바이트 형태의 데이터를 객체로 변환해서 JVM으로 상주시키는 기술이다.
직렬화
- java.io.Serializable 인터페이스를 구현해야 한다.
public class Member implements Serializable {
private String name;
private String email;
private int age;
public Member(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
@Override
public String toString() {
return String.format("Member{name='%s', email='%s', age='%s'}", name, email, age);
}
}
Java.op.ObjectOutputStream을 사용하여 직렬화를 진행
public static void main(String[] args){
Member member = new Member("ㅅㅁㅅ", "ms3796@naver.com", 25);
byte[] serializedMember;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(member);
// serializedMember -> 직렬화된 member 객체
serializedMember = baos.toByteArray();
}
}
// 바이트 배열로 생성된 직렬화 데이터를 base64로 변환
System.out.println(Base64.getEncoder().encodeToString(serializedMember));
}
역직렬화
- 직렬화 대상이 된 객체의 클래스가 class path에 존재해야 하며 import가 되어 있어야 한다.
- 자바 직렬화 대상 객체는 동일한 serialVersionID를 가지고 있어야 한다.
java.io.ObjectInputStream을 사용하여 역직렬화를 진행한다.
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)) {
// 역직렬화된 Member 객체를 읽어온다.
Object objectMember = ois.readObject();
Member member = (Member) objectMember;
System.out.println(member);
}
}
}
자바 직렬화는 어디에서 사용이 될까?
- 서블릿 세션
- 세션을 서블릿 메모리 위에서 운용한다면 직렬화가 필요 없지만, 파일로 저장하거나 세션 클러스터링, DB를 저장하는 옵션 등을 선택하게 되면 세션 자체가 직렬화가 되어 전달된다. 그래서 세션에 필요한 객체는 java.io.Serializable 인터페이스를 구현하는 것이 좋다.
- 캐시
- Encache, Redis, memcached등의 라이브러리 시스템에서 캐시 부분을 자바 직렬화된 데이터를 저장해서 사용한다. 다른 직렬화 방식도 사용되지만, 자바 직렬화가 가장 간편해서 많이 사용된다.
- 자바 RMI
- 원격 시스템 간의 메시지 교환을 위해서 사용하는 자바에서 지원하는 기술이다. 원격 시스템의 메소드를 호출 시에 전달하는 메시지를 작동으로 직렬화하고, 전달 받는 원격 시스템에서는 메시지를 역직렬화하여 사용한다.
자바 직렬화 사용 시 주의할 점
- 외부 저장소로 저장되는 데이터는 짧은 만료 시간이 아니라면 자바 직렬화 사용을 지양한다.
- 역직렬화 시 반드시 예외가 생길 수 있다는 점을 인지하고 개발한다.
- 자주 변경되는 비즈니스적인 데이터에대하여 자바 직렬화 사용을 지양한다.
- 긴 만료 시간을 가지는 데이터는 JSON등 다른 포맷을 사용하여 저장한다.
serialVersionUID
- Java직렬화 대상 객체는 동일한 serialVersionUID를 가지고 있어야 한다.
- Java직렬화에서 클래스의 버전을 식별하는 고유한 ID
- 이를 선언하지 않으면 내부적으로 클래스의 구조 정보를 이용하여 자동으로 생성된 해시 값이 할당된다
- 이 때문에 클래스의 멤버 변수가 추가되거나 삭제되면 serialVersionUID가 달라진다.
+ transient?
직렬화 대상에서 제외하고 싶은 필드에 transient 키워드를 사용한다.
이 키워드가 적용된 변수는 직렬화되지 않으며, 역직렬화 후 null 또는 기본값(0,false)으로 설정된다.
어떤 경우에 사용? > 보안상 민감한 데이터, 직렬화할 필요 없는 데이터