-
[Java] 직렬화(Seralize)란?Java 2022. 3. 24. 00:01728x90
직렬화(Seralize)란?
자바 시스템 내부에서 사용되는 Object 또는 Data를 외부의 자바 시스템에서도 사용할 수 있도록 byte 형태로 데이터를 변환하는 기술
객체를 파일 등에 저장하거나 네트워크로 전송하기 위해 연속적인 데이터로 변환하는 것
JVM의 메모리에 상주되어 있는 객체 데이터를 바이트 형태로 변환하는 기술
CSV, JSON의 경우 우리에게 많이 친숙한데 이들 또한 데이터를 문자열 형태로 확인 가능하도록 직렬화한 방법입니다.
그러면 자바에서도 CSV, JSON을 사용되는걸 직렬화를 써야 하는 이유는 무엇일까요?
정답은 없고 목적에 따라 적절하게 써야 합니다.
CSV, JSON 형태의 포맷을 이용하면 특정 라이브러리를 도입해야하며, 구조가 복잡하면 직접 매핑시켜야 하는 작업도 포함해야 합니다.
하지만 자바 직렬화는 복잡한 데이터 구조의 클래스 객체라도 직렬화 기본 조건만 지키면 큰 작업 없이 직렬화가 가능합니다.
직렬화는 어떻게 사용할까요?
직렬화 조건
자바 기본(primitive) 타입과 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; } // Getter 생략 @Override public String toString() { return String.format("Member", name, email, age); } }
Member 클래스는 Serializable 인터페이스를 상속받아서 구현하고 있습니다.
하지만 이렇게 되면 name, email, age 멤버변수 전부 직렬화 대상이 됩니다.
하지만 보안 상의 문제(비밀번호 등)나 기타 이유로 멤버변수의 일부를 제외하고 싶다면 transient를 통해 제외할 수 있습니다.
특정 멤버변수의 직렬화 제외를 위한 transient사용
private transient String password;
또한 멤버변수에 기본 변수가 아닌 다른 객체를 멤버 변수로 사용하는 경우에 해당 클래스가 Serializable 인터페이스를 구현하고 있지 않다면 직렬화 할 수 없습니다.
Serializable 인터페이스
public interface Serializable { }
Serializable은 구현체가 없습니다.
단지 직렬화 가능하다는 의미를 식별하는 역할만 수행합니다.
String 클래스의 직렬화
스트링 클래스 같은 경우는 내부적으로 Serializable을 이미 구현하고 있기 때문에 직렬화가 가능합니다.
Member클래스의 직렬화 방법
java.io.ObjectOutputStream 객체를 사용합니다.
Member member = new Member("김준우", "bababrll@naver.com", 26); 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)); }
위 예제에서는 객체를 직렬화하여 바이트 배열(byte []) 형태로 변환하였습니다.
역직렬화(Deserialize)
byte로 변환된 Data를 원래대로 Object나 Data로 변환하는 기술을 역직렬화라고 합니다.
직렬화된 바이트 형태의 데이터를 객체로 변환하는 기술
역직렬화 조건
직렬화 대상이 된 객체의 클래스가 classpath에 존재해야 하며 import 되어 있어야 합니다.
자바 직렬화 대상 객체는 동일한 serialVersionUID를 가지고 있어야 합니다.
private static final long serialVersionUID = 1L;
하지만 현재 Member 클래스가 serialVersionUID 상수를 가지고 있지는 않습니다.
SUID는 필수로 기술해야 되는 부분은 아니며, SUID가 선언되어 있지 않으면 클래스의 기본 해쉬값을 사용합니다.
즉, 내부적으로 SUID 정보가 추가됩니다.
역직렬화 예제
// 직렬화 예제에서 생성된 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); }
역직렬화시 클래스 구조 변경 문제
public class Member implements Serializable { private String name; private String email; private int age; // 생략 }
위에서 사용했던 예시인 Member클래스를 직렬화 시킨 뒤에 클래스 구조가 변경되면 어떻게 될까요?
public class Member implements Serializable { private String name; private String email; private int age; // phone 속성을 추가 private String phone; // 생략 }
우리는 보통 phone 멤버 변수가 추가되어도 기존 멤버 변수들은 채워지기를 원합니다.
하지만 데이터를 역직렬화 시킨다면 InvalidClassException이 발생합니다.
serialVersionUID의 정보가 일치하지 않기 때문에 발생하는 에러인데 SUID가 선언되어 있지 않기 때문에 "조금이라도 역직렬화 대상 클래스 구조가 바뀌면 에러가 발생해야 한다" 정도의 민감한 시스템이 아닌 이상은 클래스를 변경할 때 직접 USID 값을 관리해주어야 클래스 변경 시 혼란을 줄일 수 있습니다.
하지만 USID 값이 동일할때도 신경 써야 할 부분들이 존재합니다.
1. 멤버 변수명은 같은데 멤버 변수 타입이 바뀔 때 (string -> StringBuilder) 또는 (int -> long)
타입 예외가 발생합니다.
자바 직렬화는 상당히 타입에 엄격하다는 것을 알 수 있습니다.
2. 직렬화 자바 데이터에 존재하는 멤버 변수를 없애거나 추가했을 때
멤버 변수가 없어지는 경우에는 에러는 발생하지 않으며 값 자체만 사라지게 됩니다(데이터의 누락)
멤버 변수가 추가되는 경우는 에러가 발생하지 않으며 nul으로 해당 값이 채워집니다.
직렬화는 언제 어디서 사용될까요?
JVM의 메모리에만 존재하는 객체 데이터를 그대로 영속화(Persistence)하기 위해 사용됩니다.
시스템이 종료되더라도 없어지지 않는 장점을 가지며 영속화된 데이터이기 때문에 네트워크로 전송도 가능합니다.
또한 필요시에는 직렬화된 객체 데이터를 가져와 역직렬화하여 사용할 수 있습니다.
- 서블릿 세션(Sevelet Session)
서블릿 기반의 WAS들은 대부분 세션의 자바 직렬화를 지원하고 있습니다.
단순히 세션을 메모리 위에서 운용한다면 직렬화는 필요하지 않지만, 파일로 저장하거나 DB에 저장하는 옵션 등을 선택하게 되면 세션 자체가 직렬화되어 전달되고 저장됩니다.
-캐시
자바 시스템에서 성능을 위해 캐시라이브러리 시스템을 많이 이용합니다.
자바 시스템을 개발하다 보면 클래스가 많이 만들어지게 되고, DB를 조회한 후 가져온 데이터 객체가 실시간 형태로 요구하는 데이터가 아니라면 데이터 객체를 저장한 후 동일한 요청이 오면 DB를 다시 요청하는 것이 아니라 저장된 객체를 찾아서 응답합니다.
캐시 할 부분을 직렬화된 데이터를 저장해서 사용합니다.
-자바 RMI
RMI란 원격 시스템 간의 메시지 교환을 위해서 자바에서 지원하는 기술입니다.
보통 원격의 시스템과의 통신을 위해서 IP와 포트를 이용해서 소켓통신을 해야 하지만 RMI는 그 부분을 추상화하여 원격에 있는 시스템의 메서드를 로컬 시스템의 메서드인 것처럼 호출할 수 있습니다.
원격의 시스템의 메서드를 호출 시에 전달하는 메시지(보통 객체)를 자동으로 직렬화 시켜 사용합니다.
출처
https://techblog.woowahan.com/2550/
https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java/io/Serializable.html
https://techblog.woowahan.com/2551/
https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
'Java' 카테고리의 다른 글
[Java] 프록시 패턴이란? (0) 2022.04.08 [Java] 리플렉션이란? (0) 2022.04.07 [Java] Integer.parseInt() vs Integer.valueOf() (0) 2022.03.20 [Java] try-catch와 try-with-recources (0) 2022.03.13 [Java] Collections.sort () VS Arrays.sort() (0) 2022.03.10