Study/Java

[Java] 직렬화(Serialization)

개발개발개발 2021. 10. 25. 21:09

예전에 운영 배포 할 때 직렬화 문제를 마주한 적이 있었다. 

서버 한 대에서는 직렬화를 구현한 객체에 새로운 변수를 추가했었는데, 다른 서버 한 대에 수정한 객체를 반영하지 않아 계속 역직렬화 에러가 발생하였다.. 요청할 때의 객체와 응답 받을 때의 객체가 다른 버전의 클래스를 갖고 있던 것이 문제였다. 

직렬화에 대한 개념이 없어 에러를 찾는데 쉽지 않았다. 

 

1. 직렬화란?

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

 

 

 

2. ObjectInputStream, ObjectOutputStream

직렬화는 ObjectOutputStream을 사용하고 역직렬화에는 ObjectInputStream을 사용한다. 

각각은 InputStream과 OutputStream을 직접 상속받지만 기반 스트림을 필요로하는 보조 스트림이다. 그래서 객체를 생성할 때 입출력할 스트림을 지정해주어야 한다. 

ObjectInputStream(InputStream in)
ObjectOutputStream(OutputStream out)

 

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

직렬화를 사용하기 위해서는 해당 클래스를 직렬화가 가능하도록 해야한다. 직렬화하고자 하는 클래스가 java.io.Serializable 인터페이스를 구현하도록 하면 된다. 

 

public class dante implements Serializable{
	String bench;
	String squat;
	String dead;
}

아래와 같이 Serializable을 구현한 클래스를 상속받으면 자식클래스는 직렬화를 구현하지 않아도 된다. dante는 Serializable을 구현하지 않았지만 부모 클래스인 SuperDante가 Serializable을 구현하였으므로 dante 역시 직렬화가 가능하다.

public class SuperDante implements Serializable{
	String milp;
	String pullup;
}

public class dante extends SuperDante{
	String bench;
	String squat;
	String dead;
}

 

위의 경우 dante 객체를 직렬화하면 부모 클래스에 정의된 변수까지 함께 직렬화 된다. 

그러나 아래와 같이 부모 클래스가 Serializable을 구현하지 않았다면 자식 클래스를 직렬화할 때 부모 클래스에 정의된 변수들은 직렬화 대상에서 제외된다. 

public class SuperDante{
	String milp;
	String pullup;
}

public class dante extends SuperDante implements Serializable{
	String bench;
	String squat;
	String dead;
}

 

부모 클래스의 변수를 직렬화 대상에 포함시키기 위해서는 아래의 방법이 있다. 

1. 부모 클래스에 Serializable을 구현

2. 자식 클래스에서 부모 클래스 변수들이 직렬화되도록 처리하는 코드를 직접 추가

 

그리고 모든 클래스의 최고 조상인 Object는 Serializable을 구현하지 않았기 때문에 직렬화할 수 없다. 

직렬화하고자 하는 객체의 클래스에 직렬화가 안 되는 객체에 대한 참조를 포함한다면 "transient"를 붙여 대상에서 제외할 수 있다. 

 

public class dante implements Serializable{
	transient String bench;
	String squat;
	transient String dead;
}

 

4. 직렬화 가능한 클래스의 버전 관리 

직렬화된 객체를 역직렬화할 때는 직렬화 했을 때와 같은 클래스를 사용해야한다. 하지만 클래스의 내용이 변경된 경우, 역직렬화에 실패하며 에러가 발생한다.

"java.io.InvalidClassException: dante; local class incompatible: stream classdesc serialVersionUID=............."

 

객체가 직렬화될 때 클래스에 정의된 멤버들의 정보를 이용해서 serialVersionUID 라는 클래스의 버전을 자동생성해서 직렬화 내용에 포함된다.  이후 역직렬화 할 때 클래스의 버전을 비교하여 확인한다. 

 

 

클래스가 조금만 변경되어도 해당 클래스를 재배포하는 것은 유지보수에 어려움을 준다. 이때 클래스의 버전을 수동으로 관리해줄 필요가 있다. 

class MyDante implements Serializable{
	private static long serialVersionUID = 1L;
 	String data;
}

위와 같이 MyDante라는 직렬화 클래스가 있을 때, 클래스의 버전을 수동으로 관리하기 위해 serialVersionUID를 추가로 정의하였다. 

 

이렇게 클래스 내의 serialVersionUID를 정의해주면, 클래스의 내용이 바뀌어도 클래스의 버전이 자동생성된 값으로 변경되지 않는다. 

 

나는 임의로 1L로 정의하였지만, 서로 다른 클래스간에 같은 값을 갖지 않도록 serialver.exe를 사용해서 생성된 값을 사용할 수도 있다. 

 

이상 직렬화 끝! 

'Study > Java' 카테고리의 다른 글

[Java] Servlet(서블릿) 이란?  (0) 2022.03.01
[Java] 멀티 쓰레드(Multi Thread)  (0) 2021.09.14
[Java] 파일 입출력 정리  (0) 2021.09.08
자바 2차원 배열 정렬하기  (0) 2020.09.01