#오류에 대해 지적해 주시면 수정하겠습니다.

 

직렬화(Serialization)

객체 등 특정 구조의 데이터를 이후 복원할 수 있는 형태의 데이터로 전환하는 과정

 

역직렬화(Deserialization)

직렬화된 데이터를 복구하는 과정

 

하지만 이렇게만 보기엔 왜 이름이 직렬화인지 와닿지 않을 수 있으므로

좀더 와닿게 써보자.

 

 

 

이미지의 직렬화 과정

나는 컴퓨터 비전이라는 과목에서 직렬화를 처음 접했다.

컴퓨터비전은 영상이나 이미지속에서 특정 물체를 검출하거나 배경 검출, 색상 검출 등등 을 하는데 이미지를 바로 처리할 수 없으므로 항상 직렬화를 해야했다.

그림과 같이 이미지는 0 1로 구성된 화소들의 집합인데, 이것을 저렇게 직선으로 하나하나 펴서 배열로 처리를 하게된다. 그렇기 때문에 이름이 직렬화가 아닐까.

 

 

 

 

 


 

 

 

직렬화/역직렬화를 하기에 앞서

일단 그전에 직렬화/역직렬화를 할 객체에 Serializable interface을 상속받아야한다!

Serializable을 상속받은 User class

이 Serializable interface는 단순 마커 인터페이스이기 때문에 안이 텅텅 비어있다. 그래서 추가로 구현해야할 메소드도 없다! 단지 선언만 하면 된다.

하지만 직렬화/반직렬화 과정에서 Serializable이 있는지 없는지 체크하기 때문에 없다면 NotSerializableException이 발생하므로 반드시 상속받아놔야한다.

 

(+) instanceOf를 이용해 직렬화가 가능한 객체인지 확인하기도 좋다

 

 

직렬화

직렬화 할 땐, ObjectOutputStream class의 writeObject메소드를 이용해서 한다.

ObjectOutputSteam 인스턴스를 생성시 생성자안에 ByteStream계열을 넣어줘야한다.

직렬화 예시코드

 

 

역직렬화

역직렬화 할 땐, ObjectInputStream class의 readObject메소드를 이용해서 한다.

ObjectInputSteam 인스턴스를 생성할때 역시 생성자안에 ByteStream계열을 넣어줘야한다.

역직렬화 예시코드

 

 

 

 

방금 직렬화/역직렬화 과정을 그림으로 나타내보면 위와 같겠다

 

 

 

 

 


 

 

 

직렬화 관련 이슈사항

사실 직렬화 관련해서 이슈사항은 굉장히 많다. 이번 글에서 아래 두 가지에 대해 정리해 보겠다.

 

- 보이지 않는 생성자, readObject

- 싱글톤 패턴 객체 직렬화 시

 

 

 

보이지 않는 생성자, readObject

일단 직렬화를 할 객체를 생성했다. PositiveNumber객체로 직렬화를 위해 Serializable을 상속받았음을 확인 할 수있다.그리고 이름값을 하기 위해 필드에 선언되어있는 value는 양수의 값만 가져야 할 것이다.그렇게 되게 하기 위해 개발자는 생성자 내부에 value가 양수인지 확인하는 if문을 삽입했다.그럼 이 객체에 양수가 아닌 다른 의도하지 않은 수가 들어 올 수 없을까?

 

PositiveNumber 객체

 

정답은 NO이다. 

 

이 객체를 직렬화 하게 된다면

직렬화된 객체를 다시 반직렬화하는 과정에서 readObject 메서드를 호출하게 되는데 이과정은 개발자가 만든 생성자를 거쳐서 만들어지지 않기 때문에 value가 양수인지 확인하는 과정을 거치지 않는다.

 

따라서 악의적인 이용자가 의도적으로 내부 바이트를 수정하게 되면 다른 값으로 바뀔 수도 있다는 뜻이다.

 

이처럼 readObject()메서드는 실직적으로 숨겨진 또다른 생성자이다. 다른 생성자처럼 validation에 신경써야한다.

이를 해결하기 위해선 커스텀 readObject() 메서드를 만들면 되는데 이는 바이트 스트림을 매개변수로 받는 생성자라고 생각하면 된다. 일반 생성자와 동일하게 유효성 검사를 진행하면 앞서 말한 보안적 결함을 막을 수 있다.

 

커스텀 readObject를 생성한 PositiveNumber 객체

 

 

 

싱글톤 적용 객체 직렬화시

두번째 이슈사항은 싱글톤 객체를 직렬화 했을 때 이다.

일단 간단한 싱글톤 객체를 만들어보았다. 역시 직렬화를 위해 Serializable을 상속받았다.

이 객체를 직렬화했다가 역직렬화하게 되면 다른 객체가 생성되기 때문에 싱글톤이 의미가 없어지게 된다.

 

MySingleton 객체

 

이 문제를 해결할 수 있는 방법은 readResolve() 메서드이다.

MySingleton객체 안에 readResolve()메서드를 만들어서 이미 생성되어있는 인스턴스를 반환하도록 한다.

그러면 readObject메서드 이후 (생성되어있다면) 자동으로 readResolve()메서드를 실행한다.

이러한 방법으로 객체를 바꿔치기할 수 있다.

 

readResolve()를 생성한 MySingleton 객체

 

 

 

 

전체적인 flow

 

 

 

writeObject()이후 대상 바꾸기

readResolve 메서드는 역직렬화시 대상을 바꿔주는 일을 수행한다. 마찬가지로 직렬화 이후 대상을 바꿔주는 일을 수행하는 메서드도 있다. 바로 writeReplace 메서드이다!

 

 

이 메서드는 어디에 이용할 수 있을까?

 

 

 

 

보이지 않는 생성자의 또 다른 해결방안, 직렬화 프록시 패턴

이 메서드를 이용해서 아까 다루었던 보이지 않는 생성자 문제를 해결 할 수 있다.

아까의 PositiveNumber class를 다시 보자.

 

자세히 보면 유효한지 확인하는 것을 모든 생성자마다 해야함을 알 수 있다. (+ readObject메서드에서도)

이러한 작업은 신경써야할 포인트가 많다는 뜻이기도 하다. 이러한 단점을 보완할 수 있는 방법이 바로 직렬화 프록시 패턴을 이용하는 것이다.

 

 

PositiveNumberProxy라는 중첩클래스를 만든다. 이 친구는 직렬화만 전담하는 대변인인것이다.

직렬화를 하기위해 writeObject()메소드가 실행되면, writeReplace직렬화할 객체를 proxy객체로 바꾼다.

그러면 직렬화가 되는 객체는 본래 클래스가 아니라 프록시 클래스가 직렬화 되게 된다.

그리고 이후 직렬화된 상태를 나중에 역직렬화하게 된다면, readObject()메소드가 실행될것이고, 바로 readResolve()메소드가 실행되서 객체를 본 객체로 바꾼다.

러면 유효성 검사를 본래 객체 생성자에서만 하면되므로 코드 관리가 편안해진다.

 

직렬화 프록시 패턴

 

 

 

 


 

 

 

결론

정리해보자면 직렬화/역직렬화 할 때 앞서 말한 것들을 반드시 고려해야 하며 고려하지 않았을시 심각한 문제를 초래할 수 있다고 한다. 사실 직렬화 관련해서 이슈사항은 이 글에서 언급한 것들보다 훨씬 많다고 한다.

그렇기 때문에 직렬화말고 JSON이나 CSV와 같은 모든 플랫폼에 적용되는 형식을 사용하는 것이 바람직하다.

 

그럼 왜 공부한거ㅈ..

+ Recent posts