이번 포스팅에서는 Java 책을 통해서만 접했던 직렬화에 대해 알아보려고 합니다. O'REILLY사의 Headfirst Java
와 우아한형제들 기술블로그
를 참고하여 작성했습니다.
1. Java의 직렬화란?
- 자바 시스템 내에서 사용하는 객체 또는 데이터를 자바시스템 외에서도 사용할 수 있도록 Byte형태로 데이터를 변환하는 기술과 Byte로 변환된 데이터를 다시 객체로 변환(역직렬화)하는 기술입니다.
1.1 Java의 직렬화 방법
package com.java.ex;
public class Car implements Serializable {
...
}
우선 직렬화하려는 클래스에 Serializable
인터페이스를 구현하여 해당 클래스가 직렬화가 가능함을 알려주어야 합니다. (Serializble 인터페이스는 태그 인터페이스로 직렬화가 가능함만을 알려줄뿐 아무 메소드도 오버라이딩을 강제하지 않습니다.)
x
public class Main {
public static void main(String[] args) {
Car car1 = new Car();
FileOutput fileStream = new FileOutputStream("file.ser");
ObjectOutputStream objStream = new ObjectOutputStream(fileStream); // 위에서 생성한 fileStream객체를 인자로 받는 ObjectOutputStream 객체를 생성
objStream.writeObject(car1); // car1 객체 직렬화
objStream.close();
}
}
File로 객체의 상태를 저장하기 위해 FileOutputSteam
을 생성한 뒤 객체를 저장할 수 있는 객체인 ObjectOutputStream
을 생성하며 방금 생성한 FileOutputStream
을 인자로 전달합니다. 그후 ObjectOutputStream
을 이용하여 file에 객체의 정보를 저장합니다.
*데코레이터패턴
- 객체의 변경사항 없이 객체의 행위를 추가하거나 변경하기 위해 또 다른 객체로 감싸는 디자인패턴.
Java의 Stream들은 데코레이터 패턴
을 사용하여 디자인이 되어있습니다. 객체지향을 준수하기 위해서 입니다. 바로 각 객체가 가지고 있는 목적에 합당한 일만을 하도록 하기 위해서죠. 따라서 ObjectOutputStream
같은 클래스는 FileOutputStream
같이 파일에 접근 가능한 스트림과 연결하여 사용해야 합니다.
직렬화하려는 객체안에 또 객체가 존재하는 경우?
x
package com.java.ex;
public class House implements Serializable {
private Window window = new Window();
}
위 코드와 같이 직렬화하려는 객체안에 참조변수가 존재하는 경우에는 어떻게 직렬화를 할까요? 다행히도 Java에서는 이러한 내부 참조변수도 같이 직렬화를 자동으로 해줍니다. 하지만 위에서 말했듯이 객체가 직렬화 되기 위해서는 Serializable
인터페이스를 구현하고 있어야합니다. 따라서 내부의 객체가 Serializable
인터페이스를 구현하지 않는 경우 직렬화에 실패합니다.
특정 인스턴스 변수를 직렬화하고 싶지 않은 경우(or 저장하면 안되는 경우 or 저장을 못하는 경우).
객체를 직렬화 하여 저장할 때에 직렬화가 불가능한 경우도 있습니다. java에서 제공되는 대부분의 라이브러리는 직렬화가 가능하지만 Network 연결, Thread, File
등은 직렬화 할 수가 없습니다. 그렇다면 이러한 것들을 인스턴스 변수로 가지는 객체들은 직렬화하지 못하는 것일까요? 정답은 직렬화가 가능하다
입니다. transient
키워드를 이용하면 가능합니다.
xxxxxxxxxx
package com.java.ex;
public class Car implements Serializable {
private transient Thread thread = new Thread();
}
위와 같이 Thread 객체 앞에 transient
키워드를 붙힌다면 직렬화시 Thread객체는 같이 저장하지 않기 때문에 오류가 나지 않고 직렬화가 가능합니다.
직렬화 할 수 없는 객체를 만들어야 하는 경우가 있나요?
프로그래밍을 하다보면 객체를 저장해야할 경우가 많다고 생각할 수 있습니다. 또한 간단히 Serializable
인터페이스를 구현하는 것만으로도 직렬화가 가능하게 할 수 있습니다. 그렇다면 모든 Class가 기본적으로 상속받는 Object
Class에서 Serializable
인터페이스를 구현하도록 하지 않은 이유가 무엇일까요?
우선 Serializable
인터페이스를 Object
에서 구현했다면 어떤 Class가 직렬화가 불가능함을 표시하고 싶다면 NonSerializable
이란 인터페이스를 만들어 다시 구현해야할 것입니다. Serializable
과 NonSerializable
이 동시에 구현되어 있다면 그 Class는 직렬화가 가능한 것일까요 불가능한 것일까요 ?... 이러한 점 때문에 Serializable
은 개발자의 선택에의해 구현되도록 되어 있습니다.
그렇다면 직렬화가 되면 안되는 경우
는 언제일까요? 보안 문제로 인한 비밀번호와 같은 것들이 있을 것입니다.
1.2 Java의 역직렬화 방법
FileInputStream fileStream = new FileInputStream("file.ser"); //위에서 생성한 객체가 저장된 file을 인자로 전달.
ObjectInputStream objStream = new ObjectInputStream(fileStream); //마찬가지로 FileStream을 인자로 전달.
Car car1 = (Car)objStream.readObject(); //객체를 읽어오며 캐스팅하여 Car 참조변수에 저장.
objStream.close();
앞서 직렬화 할때 객체를 저장한 파일을 FileInputStream
객체를 생성하며 인자로 전달합니다. 객체를 읽어오기 위해 ObjectInputStream
객체를 생성하며 FileInputStream객체를 인자로 전달합니다. 그후 ObjectInpuStream
객체를 이용하여 File에 저장된 객체를 불러오며 Car
형으로 캐스팅합니다.
1.3 직렬화시 고려할 점
버전관리
당연한 말이지만 어떠한 객체를 직렬화 한 후 다시 역직렬화 하기 위해서는 직렬화 한 객체의 Class가 존재해야 합니다. 하지만 객체가 외부에 저장되어 있는 동안 Class가 변한다면 어떻게 될까요. Class가 변경되어도 역직렬화가 가능한 경우가 있지만 불가능한 경우도 있습니다. 따라서 이런때를 대비할 수 있는 방법이 존재해야 할 것입니다. 우선 어떠한 변경이 역직렬화를 가능/불가능 하게 만드는지를 파악해보겠습니다.
역직렬화에 문제를 주는 경우
- 인스턴스 변수를 삭제하는 경우
- 인스턴스 변수의 유형을 변경하는 경우
- transient로 지정하지 않았던 인스턴스 변수를 transient로 지정한 경우
- 클래스를 상속계층의 위나 아래로 변경하는 경우
- Serializable 구현을 제거한 경우
- 인스턴스 변수를 클래스 변수로 변경한 경우
역직렬화에 문제를 주지 않는 경우
- 클래스에 새로운 인스턴스 변수를 추가하는 경우
- 상속 트리에 클래스를 추가하는 경우
- 상속 트리에서 클래스를 제거하는 경우
객체를 직렬화 할때에는 객체가 속한 Class의 버전ID
번호가 찍힙니다. 그 번호를 serialVersionUID
라고 부르며 앞서 말씀드린것을 바탕으로 추론할 수 있듯이 직렬화 한 후 Class가 변경된다면 serialVersionUID
역시 다를 것입니다. 따라서 역직렬화 하는 경우 serialVersionUID
가 다르다면 역직렬화가 불가능합니다.
2. 문자열 형태의 직렬화 방법(Java 직렬화와 다름)
객체를 직렬화하는 방법은 Java의 직렬화 외에도 csv
, json
등의 문자열로 직렬화 하는 방법이 존재합니다.
csv
데이터 들을 ,(쉼표)
로 구분하여 저장하는 형식입니다. 표형태의 다량의 데이터를 직렬화시 많이 사용됩니다.
package pack;
class Member{
private String name;
private int age;
public Member(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Csv {
public static void main(String[] args) {
Member member1 = new Member("jjy", 20);
String csv = String.format("%s, %d", member1.getName(), member1.getAge());
System.out.println(csv);
}
}
json
JavaScript에서 객체를 표현하는 표기법입니다. 사람이 읽기 쉬우며 컴퓨터가 이해하기 쉬운 형태이기 때문에 구조적인 데이터를 전달하기위해 다양한 프로그래밍언어에서 사용중입니다. name:value
쌍의 형태로 데이터가 저장되며 :(콜론)
을 기준으로 구분되어집니다. json은 따로 포스팅을 통해 자세히 알아보도록 하겠습니다.
json, csv를 사용하면 되는데 java 직렬화가 따로 있는 이유 ?
이렇다할 명확한 정답이 존재하지는 않습니다. 각각의 포맷을 목적에 맞게 적절하게 사용해야 합니다. 따라서 각각의 장단점을 파악하고 있어야 합니다. java의 직렬화
의 경우 java 시스템 개발에 최적화 되어있습니다. 예를들어 복잡한 데이터 구조를 가진 클래스도 직렬화 조건을 만족하면 큰 작업 없이 바로 직렬화가 가능합니다.
json ,csv
등은 java직렬화
에 비해서 직렬화 역직렬화시 제공되는 라이브러리를 이용해야 쉽게 적용이 가능합니다.
3. 직렬화가 필요한 때가 언제인가요?
JVM의 메모리에 상주되어있는 객체를 외부에 저장하여 영속화 시킬때 사용됩니다. JVM이 종료되더라도 존재하며 네트워크로도 전송이 가능하다는 장점이 있습니다.
직렬화가 사용되는 경우
1. Servlet Session
session
을 Servlet
메모리 위에서 운용한다면 직렬화를 할 필요가 없지만 파일로 저장하거나, DB를 저장하는 옵션을 선택하게 되면 session 자체가 직렬화 되어 저장되어 전달됩니다.
2. Cache
자바시스템에서 성능향상을 위해 Cache(Redis, memcached)를 사용하게 되는데 이러한 캐시 부분을 직렬화된 데이터를 저장하여 사용하기도 한다고 합니다.
직렬화시 생각해야 할 점
- 외부에 장기간 저장
되는 경우에는 java 직렬화를 지양해야 합니다. 역직렬화 대상의 클래스가 변경이 일어날 확률이 높기 때문에 해당 데이터가 쓰레기가 될 가능성이 높기 때문입니다.
- 역직렬화시 반드시 예외(역직렬화 대상 클래스의 변경이 일어날 가능성이 높으므로)가 발생할 것으로 생각하고 개발해야 합니다.
- 자주 변경되는 클래스(비지니스 데이터 저장 클래스)는 역직렬화시 버전 충돌 문제로 java 직렬화를 사용하지 않습니다.
'Language > Java' 카테고리의 다른 글
Java - Json 과 Gson 이란? (0) | 2019.03.30 |
---|---|
Java - Eclipse에서 Gradle프로젝트 생성한 뒤 Build 하기 (0) | 2019.03.30 |
Java - JRE vs JDK (0) | 2019.01.08 |
Java - 상속 super (1) | 2019.01.05 |
Java - Call by Value와 Call by Reference (0) | 2018.12.14 |