개요
- 자바에서 직렬화란 개념을 아주 가볍게 알고 있었다. 또한 종종 캐시를 사용할 때 Serializable 인터페이스를 구현해 본 경험이 있다.
- 하지만 어떤 경우에 써야하는지 명확히 알지 못해 이번 기회에 개념을 잡아 보려고 한다.
직렬화란?
- 자바에서 직렬화란 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터 변환하는 기술을 말한다.
- 또한, 역직렬화란 바이트로 변환된 데이터를 다시 객체로 변환하는 기술을 말한다.
- 정리하면, JVM의 메모리에 상주되어 있는 객체 데이터를 바이트 형태로 변환하는 기술과 직렬화된 바이트 형태의 데이터를 객체로 변환하여 JVM으로 상주시키는 형태를 직렬화, 역직렬화라고 한다.
직렬화가 필요한 경우
- JVM의 메모리에만 상주되어 있는 객체를 시스템이 종료되더라도 사라지지 않도록 영속화 하는 경우에 주로 사용된다.
- 메모리 구조는 크게 값 형식 데이터(Value Type) 와 참조 형식 데이터(Reference Type) 이 있다는 것은 알고 있을 것이다.
- PC 마다 메모리 공간 주소가 다르기 때문에 참조 형식 데이터를 전송하는데 직렬화가 필요한 것이다.
- 서블릿 세션들은 대부분 세션의 자바 직렬화를 지원한다.
- 사실 세션을 서블릿 메모리 위에서만 운용한다면 직렬화가 필요 없겠지만 파일로 저장하거나 세션 클러스터링을 형성하거나 DB를 저장하는 옵션등을 선택하는 경우 세션 자체가 직렬화되어 저장되어 전달된다.
- 물론 캐시에서도 직렬화가 사용된다.
코드 예시
@AllArgsConstructor
public class Person implements Serializable {
private String name;
private int age;
}
- 위와 같이 Serializable 인터페이스를 구현한다.
- 아래와 같은 과정으로 직렬화 된다.
직렬화
public static void main(String[] args) {
String path = "/Users/project/test";
Person person = new Person("채마스", 28);
saveObject(path, dto);
}
public void saveObject(String path, Person dto) {
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream(path);
oos = new ObjectOutputStream(fos);
oos.writeObject(dto);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
- ObjectOutputStream 클래스를 사용해서 객체를 저장할 수 있다.
- ObjectInputStream을 사용하면 저장해놓은 객체를 읽을 수 있다.
- FileOutputStream 객체를 만든 후에 ObjectOutputStream의 매개변수로 넘기면, 객체가 파일에 저장된다.
- 마지막으로 writeObject()를 통해서 매개변수로 넘어온 객체를 저장하고 파일을 보면 파일에 객체가 저장된 것을 확인할 수 있다.
역직렬화
public static void main(String[] args) {
String path = "/Users/project/test";
loadObject(path);
}
public void loadObject(String path) {
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream(path);
ois = new ObjectInputStream(fis);
Object obj = ois.readObject();
Person dto = (Person)obj;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
if (fis != null) {
try {
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 위와 같은 방식으로 파일에 저장된 객체 정보를 읽을 수 있다.
serialVersionUID 사용 시 주의할 점
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
}
- serialVersionUID는 Java 직렬화 및 역직렬화 할때 필요한 버전 정보를 말한다.
- Java 직렬화 대상 객체는 동일한 serialVersionUID를 가지고 있어야 한다.
- serialVersionUID를 선언하지 않으면, 내부적으로 클래스의 구조 정보를 이용하여 자동으로 생성된 해시 값이 할당된다.
- 만약 객체를 직렬화하고 클래스에 멤버변수가 추가된다면 java.io.InvalidClassException 예외가 터진다.
transient와 Serializable
- transient 예약어를 사용하여 선언한 변수는 Serializable의 대상에서 제외된다.
- 패스워드와 같이 보안상으로 중요한 데이터나 꼭 저장해야 할 필요가 없는 변수에 대해서는 transient를 사용할 수 있다.
정리
- 직렬화는 서블릿 세션이나 캐시, 혹은 자바 RMI 등에 주로 사용된다.
- 자바 직렬화는 자바 시스템에서 최적화 되어 있어서 직렬화 조건만 잘 지킨다면 복잡한 데이터 구조의 클래스를 직렬화, 역직렬화 할 수 있다.
- 하지만 타입 체크가 엄격하고 용량도 json 과 같은 경량화된 데이터에 비해 훨씬 무겁다.
- 긴 만료 시간을 가지는 데이터는 JSON 등 다른 포맷을 사용하여 저장 하는 것이 바람직하다.
- 그렇기 때문에 외부 저장소로 저장되는 데이터는 짧은 만료 시간이 아니라면 자바 직렬화 사용을 지양한다.
- 또한, 역직렬화 시 반드시 예외가 생길 수 있다는 점을 인지하고 필수적으로 예외처리를 해야한다.
REFERENCES