ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Serialization, 직렬화
    자바(JAVA) 2023. 7. 26. 23:50

     

    자바 코드를 다루던 도중 살면서 처음으로 Serialization이라는 개념을 보았다.

    가볍게 지나칠 수도 있었지만 나를 자극한 호기심을 충족하고자 정리해보겠다.

    (여담이지만 조사하다보니 꽤나 깊이있고 중요한 내용인 것 같았다.)

     

    직렬화(Serialization)란?

    쉽게 말하자면,

    객체 데이터를 파일 형식으로 저장, DB에 저장, 메모리에 저장 하는 것 처럼 외부에 저장하기 위해

    바이트 스트림 형태로 바꾸는 작업을 말한다. 이렇게 파일, DB, 메모리에 저장된 바이트들을

    다시 객체로 변환하는 것을 '역 직렬화(De-serialization)'라고 일컫는다.

     

    출처: https://www.geeksforgeeks.org/serialization-in-java/

    자바에서의 직렬화는 보통 1) 자바 기본(primitive) 타입이거나 2) java.io.Serializable 인터페이스를 상속받은 객체일 경우 가능하다.

    직렬화는 Byte로 변환하는 것을 말하지만 표 형태의 데이터엔 대신 CSV를, 구조적인 데이터에는 JSON, XML 등을 사용하기도 한다.

     

    cf) '직렬화'라고 불리게 된 이유?

    프로그램에서 사용되는 데이터들은 연속적으로 위치해 있지 않고 내부적으로 포인터에 의해 참조 되고 있는데,

    이는 프로그램이 실행중인 컴퓨터에서만 인식할 수 있는 형태이다.

    다른 컴퓨터와 통신하며 데이터를 알맞게 전달하기 위해서는 이렇게 흩뿌려져 있는 데이터 모아

    포인터가 존재하지 않는 일련의 바이트 형태 만들어서 보내야 하기에 이를 보고 직렬화 라고 부르게 된 것이다.

     

    자바의 직렬화는 왜 필요한가?

    데이터를 교환할 때 그럼 JSON이나 다른 포맷을 쓰면 되는데 굳이 자바의 직렬화를 사용하는 이유가 있을까?

    이를 위해 장단점을 비교해보자.

     

    [장점]

    1. 자바 고유의 기술이기 때문에 자바 개발에서는 최적화되어있다.

    2. 자바에서 사용되는 레퍼런스 타입에 대해서도 제약없이 사용할 수 있다.

    기본형(int, string) 타입이나 배열(array)과 같은 타입들은 대부분의 프로그래밍 언어가 사용하는 타입이기 때문에 JSON 으로도 충분히 상호 이용이 가능하다. 하지만 자바의 온갖 컬렉션이나 클래스, 인터페이스 타입들, 혹은 사용자가 커스텀으로 타입을 만들어 사용하고 있는 경우 단순 파일 포맷만으로는 한계가 있다. 그래서 이들을 외부에 내보내기 위해 각 데이터를 매칭시키는 별도의 파싱(parsing)이 필요하다.

    여기서!! 직렬화를 이용하면 비록 자바스크립트와 같은 다른 시스템에서는 사용하지 못할지라도, 직렬화 기본 조건만 지킨다면 힘든 작업없이 그냥 바로 외부에 보낼 수가 있다. 역직렬화를 통해 읽어들일 때에도 데이터 타입이 자동으로 맞춰지기 때문에 자바 클래스의 기능들을 곧바로 다시 이용할 있는 것이다.

     

    cf) 레퍼런스 타입(Reference type)이란?

    => 변수나 메서드를 모은 것이 클래스, 그러한 클래스를 실체화한 것이 인스턴스이며 이를 사용하려면  인스턴스를 적정하는 정보(데이터의 주소) 알아야 한다. 그 주소가 바로 포인터이다. 포인터는 일종의 '참조'이며 클래스 같은 것들은 이러한 참조(포인터)를 보관하는 타입이라고 하여 레퍼런스 타입이라고 부른다.

     

    [단점]

     

    1. 직렬화의 경우 필요한 용량이 크다.

    DB, Cache 등에 외부에 저장할때 장기간 동안 저장하는 정보는 직렬화를 지양해야 된다는 말이 있다.

    이는 직렬화의 경우 객체에 저장된 데이터값 뿐만 아니라 타입 정보, 클래스 메타 정보까지 가지고 있어 용량을 많이 차지하기 때문이다.

    같은 정보를 직렬화로 저장하느냐 JSON으로 저장하느냐는 파일 용량 크기가 거의 2배 이상 차이가 난다고 한다.

     

    2. 역직렬화를 할 경우 보안적으로 위험할 수 있다.

    역직렬화에 쓰이는 ObjectInputStream readObject() 메서드를 호출하게 되면 객체 그래프가 역직렬화되어 classpath 안의 모든 타입의 객체를 만들어 내게 되는데, 이는 해당 타입 객체안의 모든 코드를 수행할 수 있게 된다. 이렇게 되면 나의 코드 전체로 공격 범위가 넓어지는 셈이다.

    만약 한 해커가 객체를 직렬화하는 도중 자료를 가로채 객체에 악의적은 데이터를 넣은 후 송신하고 수신자가 이를 받아 역직렬화 한다면?

    그 과정에서 악의적인 데이터의 어떤 명령이 실행되거나 한다면 그대로 공격받는 것이다.

     

    직렬화는 어디에 사용하는가?

    JVM 메모리에서만 상주되어있는 객체 데이터를 그대로 영속화(Persistence) 필요할 때 사용한다.

    쉽게 말해, 시스템이 종료되어도 사라지지 않는 데이터를 만들어서

    네트워크를 통해 전송하거나 어딘가에 데이터를 저장할 필요가 있을 때 사용하는 것이다.

    아래는 그 중 대표적인 예시들이다.

     

    [Servlet Session]

     

    - 단순히 세션을 서블릿 메모리 위에서 운용하는 것이 아라 세션 데이터를 저장 & 공유가 필요할 때

    - 세션 데이터를 데이터베이스에 저장할 때

    - WAS 로드 밸런싱이 적용되어 세션 클러스터링을 통해 서버간에 데이터 공유가 필요할 때

     

    [Cache]

     

    DB에 데이터 객체를 캐시로 만들어 저장한 후 동일한 요청이 오면 DB에 다시 요청하지 않고

    캐시를 이용하면 DB 대한 리소스를 절약할  있다.

    요즘은 캐시를 위해 Redis, Memcached 와 같은 캐시 DB를 많이 사용하는 편이다.

     

    [Remote Method Invocation]

     

    원격 시스템 간의 메시지 교환을 위해서 사용하는 자바 지원 기술,

    원격 시스템과의 통신을 위해 RMI는 원격에 있는 시스템의 메서드를 로컬 시스템의 메서드인 것처럼 호출할 수 있다.

    원격 시스템의 메서드를 호출할 때, 전달하는 메시지(보통 객체)를 자동으로 직렬화시킨다.

    그리고 이를 전달받은 원격 시스템에서는 메시지를 역직렬화하여 보게 된다.

    쉽게 말해 객체 데이터를 직렬화하여 송신하고 수신하여 역직렬화 한 후 읽는 것이다.

    그러나 최근에는 소켓을 이용하기 때문에 RMI는 잘 쓰지 않는 기술이다.

     

     

    직렬화는 어떻게 사용하는가?

    객체를 직렬화하기 위해선 아래와 같이 java.io.Serializable 인터페이스를 implements 해야 된다.

    (그렇지 않으면 NotSerializableException 런타임 예외가 발생한다고 한다.)

    public class Test implements Serializable {
        //....
    }

    또한 어떤 A라는 클래스 자기 자신이 Serializable을 구현하지 않았더라도 그것을 구현한 클래스 B를 상속받는 경우라면

    A또한 직렬화가 가능해진다.

     

    Serializable의 특징으로는 아무런 메서드가 없다것

    여기서 Serializable을 '마커 인터페이스'라고 하는데 이들은 메서드가 없다.

    실제로 Serializable의 코드를 본다면 다음과 같다.

    public interface Serializable {
    }

     

     

    보통은 클래스의 멤버 변수 모두 직렬화가 되지만 제외하고 싶은 변수가 있다면?

    아래와 같이 transient(일시적인, 순간적인)를 붙여주면 된다.

    public class Person implements Serializable {
        private String id;
        private transient String name;
    
        //....
    }

     

    만약 커스텀 클래스와 같이 클래스가 기본형이 아닌 멤버 변수가 있다면?

    그 멤버변수의 클래스들 중 어느 것 하나라도 Serializable을 구현하지 않았다면

    'java.io.InvalidClassException'이라는 에러가 발생한다.

    public class Person implements Serializable {
        private String id;
        private String name;
        private Integer age;
    
        AddressInfo addressInfo;
        //....
    }

     

    [알아둬야 할 것: serialVersionUID]

     

    Serializable를 구현하는 모든 직렬화된 클래스가 부여받는 고유 식별번호.

    이것은 클래스를 직렬화, 역직렬화하는 과정에서 각각이 동일한 것인지 확인하는데 사용된다.

    serialVersionUID 값은 지정하지 않을 경우 시스템이 런타임에 클래스의 이름, 생성자와 같은 클래스의 구조를 이용하여

    암호 해시함수를 적용해 자동으로 클래스 안에 생성하게 된다.

    그래서 클래스 내부 구성이 수정될 경우, 기존에 직렬화한 SUID와 현재 클래스의 SUID 버전이 다르기 때문에

    이를 인지하고 InvalidClassException 예외가 발생한다.

    (네트워크로 객체를 직렬화하여 전송하거나 협업을 하는 경우 수신자와 송신자 모두 같은 버전의 클래스를 가지고 있어야 한다.

    만일 클래스가 조금만 변경사항이 있다면? 모든 사용자에게 재배포해야 하는 번거로움이 생긴다.

    따라서 직렬화할 클래스는 serialVersionUID  직접 명시해주어 클래스 버전을 수동으로 관리하는 것을 권장하는 편이다. SUID 직접 명시해주면 클래스의 내용이 변경되어도 SUID가 자동 생성된 값으로 변경되지 않기 때문이다. 런타임에 SUID를 생성하는데 시간을 많이 소모하는 것도 한 가지 이유이다.)

     

    별도로 지정하고 싶다면 다음과 같이 해줄 수 있다.

    static final long serialVersionUID = 1L;

    Tip) static final long으로 선언해야 하며, 변수명도 serialVersionUID 선언해 주어야 자바에서 인식을 있다고 한다.

     

     

    Plus) 나는 여태껏 왜 직렬화를 몰랐을까?

    찾아보니 @RequestBody 와 @ResponseBody 등의 어노테이션을 사용하면 자동으로 객체를 JSON으로 직렬화하고, JSON을 객체로 역직렬화 해준다고 한다. 또 스프링 내부적으로 JSON 직렬화, 역직렬화를 위해 Jackson 이라는 라이브러리를 사용한다고 한다. 이러다 보니 딱히 신경 쓸 필요가 없었던 것 같다.

     

     

    출처:

    https://devlog-wjdrbs96.tistory.com/268

    https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%A7%81%EB%A0%AC%ED%99%94Serializable-%EC%99%84%EB%B2%BD-%EB%A7%88%EC%8A%A4%ED%84%B0%ED%95%98%EA%B8%B0

    https://velog.io/@yhlee9753/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C%EC%9E%90%EB%B0%94-item85.-%EC%9E%90%EB%B0%94-%EC%A7%81%EB%A0%AC%ED%99%94%EC%9D%98-%EB%8C%80%EC%95%88%EC%9D%84-%EC%B0%BE%EC%95%84%EB%9D%BC

    https://hudi.blog/serialization/

    https://velog.io/@hellonewtry/%EC%9E%90%EB%B0%94-%EC%A7%81%EB%A0%AC%ED%99%94%EB%9E%80-serialVersionUID-%EB%9E%80

    https://techblog.woowahan.com/2550/

     

     

     

Designed by Tistory.