Language/Java

β˜• μžλ°” 직렬화(Serializable) - μ™„λ²½ λ§ˆμŠ€ν„°ν•˜κΈ°

인파_ 2023. 2. 13. 08:17

java-Serializable

μžλ°”μ˜ 직렬화 & 역직렬화

직렬화(serialize)λž€ μžλ°” μ–Έμ–΄μ—μ„œ μ‚¬μš©λ˜λŠ” Object λ˜λŠ” Dataλ₯Ό λ‹€λ₯Έ μ»΄ν“¨ν„°μ˜ μžλ°” μ‹œμŠ€ν…œμ—μ„œλ„ μ‚¬μš© ν• μˆ˜ μžˆλ„λ‘ λ°”μ΄νŠΈ 슀트림(stream of bytes) ν˜•νƒœλ‘œ 연속전인(serial) λ°μ΄ν„°λ‘œ λ³€ν™˜ν•˜λŠ” 포맷 λ³€ν™˜ κΈ°μˆ μ„ μΌμ»«λŠ”λ‹€. κ·Έ λ°˜λŒ€ κ°œλ…μΈ μ—­μ§λ ¬ν™”λŠ”(Deserialize)λŠ” λ°”μ΄νŠΈλ‘œ λ³€ν™˜λœ 데이터λ₯Ό μ›λž˜λŒ€λ‘œ μžλ°” μ‹œμŠ€ν…œμ˜ Object λ˜λŠ” Data둜 λ³€ν™˜ν•˜λŠ” κΈ°μˆ μ΄λ‹€.

이λ₯Ό μ‹œμŠ€ν…œμ μœΌλ‘œ μ‚΄νŽ΄λ³΄λ©΄, JVM의 νž™(heap) ν˜Ήμ€ μŠ€νƒ(stack) λ©”λͺ¨λ¦¬μ— μƒμ£Όν•˜κ³  μžˆλŠ” 객체 데이터λ₯Ό 직렬화λ₯Ό 톡해 λ°”μ΄νŠΈ ν˜•νƒœλ‘œ λ³€ν™˜ν•˜μ—¬ λ°μ΄ν„°λ² μ΄μŠ€λ‚˜ 파일과 같은 μ™ΈλΆ€ μ €μž₯μ†Œμ— μ €μž₯해두고, λ‹€λ₯Έ μ»΄ν“¨ν„°μ—μ„œ 이 νŒŒμΌμ„ 가져와 μ—­μ§ˆλ ¬ν™”λ₯Ό 톡해 μžλ°” 객체둜 λ³€ν™˜ν•΄μ„œ JVM λ©”λͺ¨λ¦¬μ— μ μž¬ν•˜λŠ” κ²ƒμœΌλ‘œ 보면 λœλ‹€.

[ λ°”μ΄νŠΈ 슀트림 μ΄λž€? ]
μŠ€νŠΈλ¦Όμ€ ν΄λΌμ΄μ–ΈνŠΈλ‚˜ μ„œλ²„ 간에 μΆœλ°œμ§€ λͺ©μ μ§€λ‘œ μž…μΆœλ ₯ν•˜κΈ° μœ„ν•œ 데이터가 흐λ₯΄λŠ” ν†΅λ‘œλ₯Ό λ§ν•œλ‹€.
μžλ°”λŠ” 슀트림의 κΈ°λ³Έ λ‹¨μœ„λ₯Ό λ°”μ΄νŠΈλ‘œ 두고 있기 λ•Œλ¬Έμ—, λ„€νŠΈμ›Œν¬, λ°μ΄ν„°λ² μ΄μŠ€λ‘œ μ „μ†‘ν•˜κΈ° μœ„ν•΄ μ΅œμ†Œ λ‹¨μœ„μΈ λ°”μ΄νŠΈ 슀트림으둜 λ³€ν™˜ν•˜μ—¬ μ²˜λ¦¬ν•œλ‹€.

java-Serializable


직렬화 μ‚¬μš©μ²˜

직렬화λ₯Ό μ‘μš©ν•œλ‹€λ©΄ νœ˜λ°œμ„±μ΄ μžˆλŠ” 캐싱 데이터λ₯Ό 영ꡬ μ €μž₯이 ν•„μš”ν•  λ•Œ μ‚¬μš©ν•  μˆ˜λ„ μžˆλ‹€.

예λ₯Όλ“€μ–΄ JVM의 λ©”λͺ¨λ¦¬μ—μ„œλ§Œ μƒμ£Όλ˜μ–΄μžˆλŠ” 객체 데이터가 μ‹œμŠ€ν…œμ΄ μ’…λ£Œλ˜λ”λΌλ„ λ‚˜μ€‘μ— λ‹€μ‹œ μž¬μ‚¬μš©μ΄ 될수 μžˆμ„λ•Œ μ˜μ†ν™”(Persistence)λ₯Ό 해두면 μ’‹λ‹€. μ΄λŸ¬ν•œ νŠΉμ„±μ„ μ‚΄λ¦° μžλ°” μ§λ ¬ν™”λŠ” μ‹€μ œλ‘œλ„ μ—¬λŸ¬κ³³μ— μ‘μš©λœλ‹€. λͺ‡κ°€μ§€ μ‚΄νŽ΄λ³΄λ©΄ λ‹€μŒκ³Ό κ°™λ‹€.

 

μ„œλΈ”λ¦Ώ μ„Έμ…˜ (Servlet Session)

  • λ‹¨μˆœνžˆ μ„Έμ…˜μ„ μ„œλΈ”λ¦Ώ λ©”λͺ¨λ¦¬ μœ„μ—μ„œ μš΄μš©ν•œλ‹€λ©΄ 직렬화λ₯Ό ν•„μš”λ‘œ ν•˜μ§€ μ•Šμ§€λ§Œ, 만일 μ„Έμ…˜ 데이터λ₯Ό μ €μž₯ & κ³΅μœ κ°€ ν•„μš”ν• λ•Œ 직렬화λ₯Ό μ΄μš©ν•œλ‹€.
  • μ„Έμ…˜ 데이터λ₯Ό λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯ν• λ•Œ
  • ν†°μΊ£μ˜ μ„Έμ…˜ ν΄λŸ¬μŠ€ν„°λ§μ„ 톡해 각 μ„œλ²„κ°„μ— 데이터 κ³΅μœ κ°€ ν•„μš”ν• λ•Œ

 

μΊμ‹œ (Cache)

  • λ°μ΄ν„°λ² μ΄μŠ€λ‘œλΆ€ν„° μ‘°νšŒν•œ 객체 데이터λ₯Ό λ‹€λ₯Έ λͺ¨λ“ˆμ—μ„œλ„ ν•„μš”ν• λ•Œ 재차 DBλ₯Ό μ‘°νšŒν•˜λŠ” 것이 μ•„λ‹Œ, 객체λ₯Ό μ§λ ¬ν™”ν•˜μ—¬ λ©”λͺ¨λ¦¬λ‚˜ μ™ΈλΆ€ νŒŒμΌμ— μ €μž₯ν•΄ λ‘μ—ˆλ‹€κ°€ μ—­μ§λ ¬ν™”ν•˜μ—¬ μ‚¬μš©ν•˜λŠ” μΊμ‹œ λ°μ΄ν„°λ‘œμ„œ 이용이 κ°€λŠ₯ν•˜λ‹€.
  • λ¬Όλ‘  μžλ°” 직렬화λ₯Ό μ΄μš©ν•΄μ„œλ§Œ μΊμ‹œλ₯Ό μ €μž₯ν•  수 μžˆλŠ” 것은 μ•„λ‹ˆμ§€λ§Œ μžλ°” μ‹œμŠ€ν…œμ—μ„œ λ§ŒνΌμ€ κ΅¬ν˜„μ΄ κ°€μž₯ κ°„νŽΈν•˜κΈ° λ•Œλ¬Έμ— 많이 μ‚¬μš©λœλ‹€κ³  보면 λœλ‹€.
  • 단, μš”μ¦˜μ€ Redis, Memcached 와 같은 μΊμ‹œ DBλ₯Ό 많이 μ‚¬μš©ν•˜λŠ” νŽΈμ΄λ‹€.

 

μžλ°” RMI (Remote Method Invocation)

  • μžλ°” RMIλŠ” 원격 μ‹œμŠ€ν…œ κ°„μ˜ λ©”μ‹œμ§€ κ΅ν™˜μ„ μœ„ν•΄μ„œ μ‚¬μš©ν•˜λŠ” μžλ°”μ—μ„œ μ§€μ›ν•˜λŠ” κΈ°μˆ μ΄λ‹€.
  • 이 메세지에 객체 데이터λ₯Ό μ§λ ¬ν™”ν•˜μ—¬ μ†‘μ‹ ν•˜λŠ” 것이닀.
  • μ΅œκ·Όμ—λŠ” μ†ŒμΌ“μ„ μ΄μš©ν•˜κΈ° λ•Œλ¬Έμ— μ•ˆμ“°μ΄λŠ” κΈ°μˆ μ΄λ‹€.

직렬화 vs JSON 비ꡐ

이처럼 μžλ°” μ§λ ¬ν™”λŠ” μ™ΈλΆ€ νŒŒμΌμ΄λ‚˜ λ„€νŠΈμ›Œν¬λ₯Ό 톡해 ν΄λΌμ΄μ–ΈνŠΈ 간에 객체 데이터λ₯Ό μ£Όκ³  받을 λ•Œ μ‚¬μš©λœλ‹€.

그런데 이미 κ°œλ°œμ„ 쑰금 해보신 λ…μžλΆ„λ“€μ΄λΌλ©΄ 문득 이런 생각이 λ“€ 것이닀. CSV, JSON μ΄λΌλŠ” ν›Œλ₯­ν•œ 데이터 포맷이 μžˆλŠ”λ° ꡳ이 직렬화가 ν•„μš”ν•˜λŠλƒλΌλŠ” μ˜λ¬Έμ μ΄λ‹€.

μ‹€μ œλ‘œ JSON은 μ›Ή(Web) 뿐만 μ•„λ‹ˆλΌ κ²Œμž„ μͺ½μ—μ„œλ„ μ„€μ • 파일둜 μ“°μ΄κ±°λ‚˜ 데이터λ₯Ό κ΅ν™˜ν• λ•Œ λ²”μš©μ μœΌλ‘œ μ‚¬μš©λœλ‹€. 그리고 μ§λ ¬ν™”λŠ” μ˜€λ‘œμ§€ μžλ°” ν”„λ‘œκ·Έλž¨μ—μ„œλ§Œ μ‚¬μš©μ΄ κ°€λŠ₯ν•˜μ§€λ§Œ, JSON ν˜•νƒœλ‘œ 객체 데이터λ₯Ό μ €μž₯해두면 파이썬, μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œλ„ λ²”μš©μ μœΌλ‘œ μ‚¬μš©μ΄ κ°€λŠ₯ν•˜λ‹€.

java-Serialize-json

κ·Έλ ‡λ‹€λ©΄ κ·Έλƒ₯ JSON을 μ΄μš©ν•˜λ©΄ λ˜μ§€ ꡳ이 직렬화λ₯Ό κ³΅λΆ€ν•΄μ„œ μ‚¬μš©ν•΄μ•Ό ν•˜λŠ” μ΄μœ κ°€ 뭐가 μžˆμ„κΉŒ?

이에 λŒ€ν•΄μ„œ λ¨Όμ € μžλ°” μ§λ ¬ν™”μ˜ μž₯점에 λŒ€ν•΄ μ•Œμ•„λ³΄μžλ©΄ λ‹€μŒκ³Ό κ°™λ‹€.

 

μžλ°” 직렬화 μž₯점

μ²«λ²ˆμ§ΈλŠ”, μ§λ ¬ν™”λŠ” μžλ°”μ˜ 고유 기술인 만큼 λ‹Ήμ—°νžˆ μžλ°” μ‹œμŠ€ν…œμ—μ„œ κ°œλ°œμ— μ΅œμ ν™”λ˜μ–΄ μžˆλ‹€.

λ‘λ²ˆμ§ΈλŠ”, μžλ°”μ˜ κ΄‘ν™œν•œ 레퍼런슀 νƒ€μž…μ— λŒ€ν•΄ μ œμ•½ 없이 외뢀에 내보낼 수 μžˆλ‹€λŠ” 것이닀.

예λ₯Όλ“€μ–΄ κΈ°λ³Έν˜•(int, double, string) νƒ€μž…μ΄λ‚˜ λ°°μ—΄(array)κ³Ό 같은 νƒ€μž…λ“€μ€ μ™ λ§Œν•œ ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄κ°€ κ³΅ν†΅μ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” νƒ€μž…μ΄κΈ° λ•Œλ¬Έμ—, μ΄λŸ¬ν•œ 값듀을 JSON μœΌλ‘œλ„ μΆ©λΆ„νžˆ μƒν˜Έ 이용이 κ°€λŠ₯ν•˜λ‹€.

ν•˜μ§€λ§Œ μžλ°”μ˜ μ˜¨κ°– μ»¬λ ‰μ…˜μ΄λ‚˜ 클래슀, μΈν„°νŽ˜μ΄μŠ€ νƒ€μž…λ“€μ€ μ–΄λ–¨κΉŒ? ν˜Ήμ€ μ‚¬μš©μžκ°€ μ»€μŠ€ν…€μœΌλ‘œ μžλ£Œν˜• νƒ€μž…μ„ λ§Œλ“€μ–΄ μ‚¬μš©ν•˜κ³  μžˆλŠ” 경우, λ‹¨μˆœ 파일 ν¬λ§·λ§ŒμœΌλ‘œλŠ” νƒ€μž… κ°―μˆ˜κ°€ ν•œκ³„κ°€ μžˆλ‹€. κ·Έλž˜μ„œ 이듀을 외뢀에 내보내기 μœ„ν•΄μ„  각 데이터λ₯Ό λ§€μΉ­μ‹œν‚€λŠ” λ³„λ„μ˜ νŒŒμ‹±(parsing)이 ν•„μš”ν•˜λ‹€.

그에 λ°˜ν•΄, 직렬화λ₯Ό μ΄μš©ν•˜λ©΄ 비둝 νŒŒμ΄μ¬μ΄λ‚˜ μžλ°”μŠ€ν¬λ¦½νŠΈμ™€ 같은 λ‹€λ₯Έ μ‹œμŠ€ν…œμ—μ„œλŠ” μ‚¬μš©ν•˜μ§€λŠ” λͺ»ν• μ§€λΌλ„, 직렬화 κΈ°λ³Έ 쑰건만 지킨닀면 ν•˜λ“œν•œ μž‘μ—…μ—†μ΄ κ·Έλƒ₯ λ°”λ‘œ 외뢀에 λ³΄λ‚Όμˆ˜κ°€ μžˆλ‹€. 그리고 역직렬화λ₯Ό 톡해 읽어듀이면 데이터 νƒ€μž…μ΄ μžλ™μœΌλ‘œ λ§žμΆ°μ§€κΈ° λ•Œλ¬Έμ— μžλ°” 클래슀의 κΈ°λŠ₯듀을 κ³§λ°”λ‘œ λ‹€μ‹œ μ΄μš©ν•  수 μžˆλŠ” 것이닀. 별닀λ₯Έ νŒŒμ‹± 없이 말이닀. κ·Έλž˜μ„œ μ§λ ¬ν™”λœ λ¬Έμžμ—΄μ„ λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯해두고 κΊΌλ‚΄ 쓰기도 ν•œλ‹€.

 

μ΄λŸ¬ν•œ μ§λ ¬ν™”μ˜ κ³ μœ ν•œ μž₯점듀이 μžˆμ§€λ§Œ, λ°˜λ©΄μ— 치λͺ…적인 단점듀도 μ‘΄μž¬ν•œλ‹€. (μ΄λŠ” ν¬μŠ€νŒ… λ§ˆμ§€λ§‰μ—μ„œ 닀뀄본닀)

그리고 μš”μ¦˜μ€ λ²”μš©μ μΈ JSON을 μ΄μš©ν•˜λŠ” μΆ”μ„Έκ°€ 점점 늘고 μžˆλ‹€. λ”°λΌμ„œ JSON 이냐 직렬화 이냐 에 λŒ€ν•œ λͺ…ν™•ν•œ 정닡은 μ—†κ³  'λͺ©μ μ— 따라 적절히 써야 ν•œλ‹€' μ •λ„λ‘œ 정리할 수 μžˆμ„ 것 κ°™λ‹€.


μžλ°” 직렬화 μ‚¬μš©λ²•

 

객체 직렬화 & 역직렬화 ν•˜κΈ°

 

Serializable μΈν„°νŽ˜μ΄μŠ€

μš°μ„  객체λ₯Ό μ§λ ¬ν™”ν•˜κΈ° μœ„ν•΄μ„  java.io.Serializable μΈν„°νŽ˜μ΄μŠ€λ₯Ό implements ν•΄μ•Ό λœλ‹€. 그렇지 μ•ŠμœΌλ©΄ NotSerializableException λŸ°νƒ€μž„ μ˜ˆμ™Έκ°€ λ°œμƒλœλ‹€.

Serializable μΈν„°νŽ˜μ΄μŠ€λŠ” μ•„λ¬΄λŸ° λ‚΄μš©λ„ μ—†λŠ” 마컀 μΈν„°νŽ˜μ΄μŠ€ λ‘œμ„œ, 직렬화λ₯Ό κ³ λ €ν•˜μ—¬ μž‘μ„±ν•œ ν΄λž˜μŠ€μΈμ§€λ₯Ό νŒλ‹¨ν•˜λŠ” κΈ°μ€€μœΌλ‘œ μ‚¬μš©λœλ‹€.

Serializable

import java.io.Serializable;

class Customer implements Serializable {
    int id; // 고객 아이디
    String name; // 고객 λ‹‰λ„€μž„
    String password; // 고객 λΉ„λ°€λ²ˆν˜Έ
    int age; // 고객 λ‚˜μ΄

    public Customer(int id, String name, String password, int age) {
        this.id = id;
        this.name = name;
        this.password = password;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", password='" + password + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

 

ObjectOutputStream 객체 직렬화

직렬화(μŠ€νŠΈλ¦Όμ— 객체λ₯Ό 좜λ ₯) μ—λŠ” ObjectOutputStream을 μ‚¬μš©ν•œλ‹€.

객체가 μ§λ ¬ν™”λ λ•Œ 였직 객체의 μΈμŠ€ν„΄μŠ€ ν•„λ“œκ°’ λ§Œμ„ μ €μž₯ν•œλ‹€. static ν•„λ“œλ‚˜ λ©”μ„œλ“œλŠ” μ§λ ¬ν™”ν•˜μ—¬ μ €μž₯ν•˜μ§€ μ•ŠλŠ”λ‹€.

μ•„λž˜ μ½”λ“œλŠ” μ™ΈλΆ€ νŒŒμΌμ— 객체λ₯Ό μ§λ ¬ν™”ν•˜μ—¬ μ €μž₯ν•˜λŠ” μ˜ˆμ œμ΄λ‹€.

public static void main(String[] args) {
    // 직렬화할 고객 객체
    Customer customer = new Customer(1, "홍길동", "123123", 40);

    // μ™ΈλΆ€ 파일λͺ…
    String fileName = "Customer.ser";

    // 파일 슀트림 객체 생성 (try with resource)
    try (
            FileOutputStream fos = new FileOutputStream(fileName);
            ObjectOutputStream out = new ObjectOutputStream(fos)
    ) {
        // 직렬화 κ°€λŠ₯ 객체λ₯Ό λ°”μ΄νŠΈ 슀트림으둜 λ³€ν™˜ν•˜κ³  νŒŒμΌμ— μ €μž₯
        out.writeObject(customer);

    } catch (IOException e) {
        e.printStackTrace();
    }
}

ObjectOutputStream

μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜λ©΄ Customer.ser 파일이 μƒμ„±λœλ‹€. νŒŒμΌμ„ .ser ν™•μž₯λͺ…을 μ‚¬μš©ν–ˆλŠ”λ°, .txt 둜 해도 문제 μ—†λ‹€. λ‹€λ§Œ μ§λ ¬ν™”λœ νŒŒμΌμ΄λΌλŠ” 것을 λͺ…μ‹œν•˜λŠ” 것이 μ’‹κΈ° λ•Œλ¬Έμ— 넀이밍을 .ser μ΄λ‚˜ .obj 둜 많이 μ§€μ •ν•˜λŠ” νŽΈμ΄λ‹€.

ObjectOutputStream

그리고 파일 λ‚΄μš©μ„ 보면 μ‚¬λžŒμ΄ 읽을 수 μ—†λŠ” 문자 ν˜•νƒœλ‘œ λ˜μ–΄μžˆλŠ” κ±Έ λ³Ό 수 μžˆλ‹€.

ObjectOutputStream

 

ObjectInputStream 객체 역직렬화

역직렬화(μŠ€νŠΈλ¦ΌμœΌλ‘œλΆ€ν„° κ°μ²΄λ₯Ό μž…λ ₯)μ—λŠ” ObjectInputStream을 μ‚¬μš©ν•œλ‹€.

단, 역직렬화 ν• λ•Œ μ£Όμ˜μ‚¬ν•­μ΄ μžˆλŠ”λ°, 직렬화 λŒ€μƒμ΄ 된 객체의 ν΄λž˜μŠ€κ°€ μ™ΈλΆ€ 클래슀라면, ν΄λž˜μŠ€ 경둜(Class Path)에 μ‘΄μž¬ν•΄μ•Ό ν•˜λ©° import λœ μƒνƒœμ—¬μ•Ό ν•œλ‹€.

μ•„λž˜ μ½”λ“œλŠ” μ™ΈλΆ€ νŒŒμΌμ„ 읽어 μ—­μ§λ ¬ν™”ν•˜μ—¬ λ‹€μ‹œ μžλ°” 객체화 ν•˜λŠ” μ˜ˆμ œμ΄λ‹€.

public static void main(String[] args) {
    // μ™ΈλΆ€ 파일λͺ…
    String fileName = "Customer.ser";

    // 파일 슀트림 객체 생성 (try with resource)
    try(
            FileInputStream fis = new FileInputStream(fileName);
            ObjectInputStream in = new ObjectInputStream(fis)
    ) {
        // λ°”μ΄νŠΈ μŠ€νŠΈλ¦Όμ„ λ‹€μ‹œ μžλ°” 객체둜 λ³€ν™˜ (μ΄λ•Œ μΊμŠ€νŒ…μ΄ ν•„μš”)
        Customer deserializedCustomer = (Customer) in.readObject();
        System.out.println(deserializedCustomer);

    } catch (IOException | ClassNotFoundException e) {
        e.printStackTrace();
    }
}

ObjectInputStream

μ΄λ ‡κ²Œ 역직렬화λ₯Ό μ΄μš©ν•˜κ²Œλ˜λ©΄, μ§λ ¬ν™”λœ μ™ΈλΆ€ 파일만 있으면 μƒμ„±μžλ‘œ 객체 μ΄ˆκΈ°ν™” 없이 λ°”λ‘œ 객체에 정보λ₯Ό 가져와 μΈμŠ€ν„΄μŠ€ν™” ν•˜μ—¬ μ‚¬μš©ν•  수 있게 λ˜λŠ” λ§ˆλ²• 같은 κΈ°λŠ₯을 얻을 수 있게 λœλ‹€.

ObjectInputStream

 

직렬화 개체λ₯Ό 리슀트둜 관리

λ§Œμ•½ μ—¬λŸ¬κ°œμ˜ 객체λ₯Ό μ§λ ¬ν™”ν•˜κ³  이λ₯Ό 역직렬화 ν•œλ‹€λ©΄ μ£Όμ˜ν•΄μ•Όν•  사항이 μžˆλ‹€.

역직렬화 ν•  λ•ŒλŠ” 직렬화할 λ•Œμ˜ μˆœμ„œμ™€ 일치 ν•΄μ•Ό λœλ‹€λŠ” 점인데, 예λ₯Όλ“€μ–΄ 객체 customer1, customer2, customer3 μˆœμ„œλ‘œ 직렬화 ν–ˆλ‹€λ©΄, 역직렬화 ν•  λ•Œλ„ customer1, customer2, customer3 μ˜ μˆœμ„œλ‘œ λ°›μ•„μ•Ό λœλ‹€. (νŒŒμΌμ— 직렬화 μˆœμ„œλŒ€λ‘œ λ°”μ΄νŠΈ λ¬Έμžκ°€ κΈ°μž¬λ˜μ–΄ μ§€λ‹ˆ λ‹Ήμ—°ν•œ μ†Œλ¦¬μ΄κΈ΄ ν•˜λ‹€)

λ”°λΌμ„œ 직렬화할 객체가 λ§Žλ‹€λ©΄ ArrayList와 같은 μ»¬λ ‰μ…˜μ— μ €μž₯ν•΄μ„œ 관리 ν•˜λŠ”κ²ƒμ΄ μ’‹λ‹€. ArrayList ν•˜λ‚˜λ§Œ μ—­μ§λ ¬ν™”ν•˜λ©΄ λ˜λ―€λ‘œ 객체의 μˆœμ„œλ₯Ό κ³ λ €ν•  ν•„μš”κ°€ 없어지기 λ•Œλ¬Έμ΄λ‹€. μ΄λŠ” μœ„μ˜ '직렬화와 JSON 비ꡐ 파트'μ—μ„œ μ§λ ¬ν™”μ˜ μž₯점에 λŒ€ν•΄ μ†Œκ°œν• λ•Œ μžλ°” 고유의 클래슀, μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ³ λŒ€λ‘œ 직렬화 / 역직렬화할 수 μžˆλ‹€κ³  λ§ν•œ λ°” μžˆλ‹€.

public static void main(String[] args) throws IOException, ClassNotFoundException {
    // 직렬화할 고객 객체
    Customer hongildong = new Customer(1, "홍길동", "123123", 40);
    Customer sejong = new Customer(2, "μ„Έμ’…λŒ€μ™•", "4556456", 55);
    Customer jumong = new Customer(3, "μ£Όλͺ½", "789789", 25);

    // μ™ΈλΆ€ 파일λͺ…
    String fileName = "Customer.ser";

    // 리슀트 생성
    List<Customer> customerList = new ArrayList<>();
    customerList.add(hongildong);
    customerList.add(sejong);
    customerList.add(jumong);

    // 리슀트 자체λ₯Ό 직렬화 ν•˜κΈ°
    FileOutputStream fos = new FileOutputStream(fileName);
    ObjectOutputStream out = new ObjectOutputStream(fos);
    out.writeObject(customerList);
    
    out.close();

    // 역직렬화 ν•˜μ—¬ 리슀트 객체에 λ„£κΈ°
    FileInputStream fis = new FileInputStream(fileName);
    ObjectInputStream in = new ObjectInputStream(fis);
    List<Customer> deserializedCustomerList = (List<Customer>) in.readObject();
    
    in.close();
    
    System.out.println(deserializedCustomerList);
}

java-Serializable


직렬화 μš”μ†Œ μ œμ™Έ

객체의 λͺ¨λ“  μΈμŠ€ν„΄μŠ€λ₯Ό μ§λ ¬ν™”ν•˜κΈ°μ—λŠ” λ„ˆλ¬΄ λ¬΄κ²κ±°λ‚˜ ν˜Ήμ€ μ€‘μš”ν•œ μ •λ³΄λŠ” 외뢀에 λ…ΈμΆœμ‹œν‚€κ³  싢지 μ•Šμ€ 경우, 직렬화할 μš”μ†Œλ₯Ό 직접 선택할 ν•„μš”κ°€ μžˆλ‹€. 

 

transient ν‚€μ›Œλ“œ

κ°„λ‹¨ν•˜κ²Œ λ³€μˆ˜ μ •μ˜λ¬Έ μ˜†μ— transient ν‚€μ›Œλ“œλ₯Ό λͺ…μ‹œν•΄μ£Όλ©΄ μ•Œμ•„μ„œ 직렬화 λŒ€μƒμ—μ„œ μ œμ™Έ λ˜λ„λ‘ ν•  수 μžˆλ‹€. transientκ°€ 뢙은 μΈμŠ€ν„΄μŠ€ λ³€μˆ˜μ˜ 값은 κ·Έ νƒ€μž…μ˜ κΈ°λ³Έκ°’μœΌλ‘œ 직렬화 λœλ‹€.

  • Primitive νƒ€μž… : 각 νƒ€μž…μ˜ λ””ν΄νŠΈ κ°’ (intλŠ” 0)
  • Reference νƒ€μž… : null

단, 직렬화 λŒ€μƒμ—μ„œ μ œμ™Έν•˜λŠ”λ° μžˆμ–΄ κ·Έ 데이터가 객체에 μ‹€μ œλ‘œ ν•„μš”κ°€ μ—†λŠ”μ§€, μ œμ™Έν•˜μ˜€μ„ κ²½μš°μ— μ„œλΉ„μŠ€ μž₯애에 이상이 μ—†λŠ”μ§€μ— λŒ€ν•œ κ³ λ €λ₯Ό ν•΄μ•Όν•œλ‹€.

java-transient

class Customer implements Serializable {
    int id; 
    String name; 
    transient String password; // 직렬화 λŒ€μƒμ—μ„œ μ œμ™Έ
    int age; 

    public Customer(int id, String name, String password, int age) {
        this.id = id;
        this.name = name;
        this.password = password;
        this.age = age;
    }
    
    ...
}

java-transient


μ»€μŠ€ν…€ 직렬화

 

readObject / writeObject μž¬μ •μ˜

직렬화 & μ—­μ§λ ¬ν™”ν• λ•Œ ν˜ΈμΆœλ˜λŠ” readObject() 와 writeObject() λŠ” 기본적으둜 λͺ¨λ“  μš”μ†Œμ— λŒ€ν•΄ μžλ™ 직렬화 ν•œλ‹€. 그런데 이 λ©”μ„œλ“œλ“€μ„ 직렬화할 ν΄λž˜μŠ€μ— λ³„λ„λ‘œ μž¬μ •μ˜ ν•΄μ£Όλ©΄ 직렬화λ₯Ό μ„ νƒμ μœΌλ‘œ μ‘°μž‘ν•  수 있게 λœλ‹€. 이λ₯Ό μ»€μŠ€ν…€ 직렬화 라고도 λΆˆλ¦¬μš΄λ‹€.

예λ₯Όλ“€μ–΄ Customer 클래슀의 λΉ„λ°€λ²ˆν˜Έ ν•„λ“œκ°’μ€ λ―Όκ°ν•œ 정보라 μ§λ ¬ν™”ν•˜μ§€ μ•Šκ² λ‹€ν•˜λ©΄, writeObject λ©”μ„œλ“œμ—μ„œ password ν•„λ“œ λΆ€λΆ„λ§Œ λΉΌκ³  μ“°κΈ° λ™μž‘μ„ ν•˜λ„λ‘ μž¬μ •μ˜ 해쀄 수 μžˆλ‹€. 이외에도 μƒμ„Έν•œ μ‘°κ±΄λ¬Έμ΄λ‚˜ λ‘œμ§μ„ κ°€λ―Έν•΄μ„œ 직렬화λ₯Ό μ»€μŠ€ν…€ μ‘°μž‘ ν•  수 μžˆλ‹€.

java-Serializable
java-Serializable

class Customer implements Serializable {
    int id; // 고객 아이디
    String name; // 고객 λ‹‰λ„€μž„
    String password; // 고객 λΉ„λ°€λ²ˆν˜Έ
    int age; // 고객 λ‚˜μ΄

    public Customer(int id, String name, String password, int age) {
        this.id = id;
        this.name = name;
        this.password = password;
        this.age = age;
    }

    // 직렬화 λ™μž‘ μž¬μ •μ˜
    private void writeObject(ObjectOutputStream out) throws IOException{
        out.writeInt(id);
        out.writeObject(name);
        out.writeInt(age);
    }

    // 역직렬화 λ™μž‘ μž¬μ •μ˜
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
        this.id = in.readInt();
        this.name = (String) in.readObject();
        this.age = in.readInt();
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", password='" + password + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
    Customer user = new Customer(1, "홍길동", "123123", 40);
    String fileName = "Customer.ser";

    // 직렬화 ν•˜κΈ° (ν•œμ€„λ‘œ ν‘œν˜„)
    ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(fileName)));
    out.writeObject(user);
    out.close();

    // 역직렬화 ν•˜κΈ° (ν•œμ€„λ‘œ ν‘œν˜„)
    ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(fileName)));
    Customer deserialized = (Customer) in.readObject();
    in.close();

    System.out.println(deserialized);
}

java-Serializable


객체 상속 κ΄€κ³„μ—μ„œμ˜ 직렬화

λ§Œμ•½ λΆ€λͺ¨-μžμ‹ 상속 κ΄€κ³„μ—μ„œ λΆ€λͺ¨ ν΄λž˜μŠ€κ°€ Serializable을 κ΅¬ν˜„ν–ˆλ‹€λ©΄ μžμ‹ ν΄λž˜μŠ€λŠ” Serializable을 κ΅¬ν˜„ν•˜μ§€ μ•Šμ•„λ„ 직렬화가 κ°€λŠ₯ν•˜λ‹€. 그러면 λ°˜λŒ€λ‘œ λΆ€λͺ¨ ν΄λž˜μŠ€λŠ” Serializable을 κ΅¬ν˜„ν•˜μ§€ μ•Šκ³  μžμ‹ 클래슀만 κ΅¬ν˜„ν–ˆλ‹€λ©΄ μ–΄λ–€ λ°©μ‹μœΌλ‘œ μ§λ ¬ν™”λ κΉŒ?

μ§λ ¬ν™”ν• λ•Œ λΆ€λͺ¨ 클래슀의 μΈμŠ€ν„΄μŠ€ ν•„λ“œλŠ” λ¬΄μ‹œλ˜κ³  μžμ‹ ν•„λ“œλ§Œ 직렬화가 λœλ‹€. λ”°λΌμ„œ μƒμœ„ 클래슀의 ν•„λ“œκΉŒμ§€ μ§λ ¬ν™”ν•˜λ €λ©΄ λΆ€λͺ¨ ν΄λž˜μŠ€κ°€ Serializable을 κ΅¬ν˜„ν•˜λ„λ‘ μ„€μ •ν•˜λ˜μ§€, μœ„μ—μ„œ 닀뀄본 writeObject / readObject λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•˜μ—¬ 직접 직렬화 μ½”λ“œλ₯Ό μΆ”κ°€ ν•˜λ©΄ λœλ‹€.

예λ₯Ό λ“€μ–΄ UserInfo ν΄λž˜μŠ€κ°€ UserAccountλ₯Ό μƒμ†ν•˜λŠ”λ°, UserInfo ν΄λž˜μŠ€μ—λ§Œ Serializable이 κ΅¬ν˜„λ˜μ–΄μžˆλ‹€κ³  ν•˜μž. UserAccount λΆ€λͺ¨ 클래슀의 ν•„λ“œκΉŒμ§€ μ§λ ¬ν™”ν•˜κ³  μ‹Άλ‹€λ©΄ UserAccount에 직렬화 λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ ν•΄μ£Όκ³  μΆ”κ°€λ‘œ 직렬화할 뢀뢄을 κΈ°μž¬ν•˜λ©΄ λœλ‹€.

java-Serializable

class UserAccount {
    String name;
    String password;

    // ! κΈ°λ³Έ μƒμ„±μž μ—†μœΌλ©΄ InvalidClassException : no valid constructor λ°œμƒ
    public UserAccount() {
    }

    UserAccount(String name, String password) {
        this.name = name;
        this.password = password;
    }
}
class UserInfo extends UserAccount implements Serializable {
    int age;
    int height;
    boolean marreid;

    UserInfo(String name, String password, int age, int height, boolean marreid) {
        super(name, password);
        this.age = age;
        this.height = height;
        this.marreid = marreid;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        // λΆ€λͺ¨ ν•„λ“œ 직렬화
        out.writeUTF(name);
        out.writeUTF(password);

        // μžμ‹  ν•„λ“œ 직렬화 (λ©”μ„œλ“œλ₯Ό 톡해 ν•œλ²ˆμ— 처리)
        out.defaultWriteObject();
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        // λΆ€λͺ¨ ν•„λ“œ 역직렬화
        name = in.readUTF();
        password = in.readUTF();

        // μžμ‹  ν•„λ“œ 역직렬화 (λ©”μ„œλ“œλ₯Ό 톡해 ν•œλ²ˆμ— 처리)
        in.defaultReadObject();
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "age=" + age +
                ", height=" + height +
                ", marreid=" + marreid +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
    UserInfo u1 = new UserInfo("홍길동", "123123", 33, 170, true);
    UserInfo u2 = new UserInfo("μž„κΊ½μ •", "456456", 12, 180, false);
    String fileName = "UserInfo.ser";
    
    // 직렬화
    ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(fileName)));
    
    out.writeObject(u1);
    out.writeObject(u2);
    
    out.close();

    // 역직렬화
    ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(fileName)));

    // 객체λ₯Ό μ½μ„λ•ŒλŠ” 좜λ ₯ν•œ μˆœμ„œμ™€ 일치
    UserInfo u3 = (UserInfo) in.readObject();
    UserInfo u4 = (UserInfo) in.readObject();

    System.out.println(u3);
    System.out.println(u4);

    in.close();
}

java-Serializable


μžλ°” 직렬화 버전 관리

 

SerialVersionUID

Serializable μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜λŠ” λͺ¨λ“  μ§λ ¬ν™”λœ ν΄λž˜μŠ€λŠ” serialVersionUID(μ΄ν•˜ SUID) μ΄λΌλŠ” 고유 μ‹λ³„λ²ˆν˜Έλ₯Ό λΆ€μ—¬ λ°›λŠ”λ‹€. 이 식별 IDλŠ” 클래슀λ₯Ό 직렬화, 역직렬화 κ³Όμ •μ—μ„œ λ™μΌν•œ νŠΉμ„±μ„ κ°–λŠ”μ§€ ν™•μΈν•˜λŠ”λ° μ‚¬μš©λœλ‹€. κ·Έλž˜μ„œ 클래슀 λ‚΄λΆ€ ꡬ성이 μˆ˜μ •λ  경우, 기쑴에 μ§λ ¬ν™”ν•œ SUID와 ν˜„μž¬ 클래슀의 SUID 버전이 λ‹€λ₯΄κΈ° λ•Œλ¬Έμ— 이λ₯Ό μΈμ§€ν•˜κ³  InvalidClassException μ˜ˆμ™Έκ°€ λ°œμƒμ‹œμΌœ κ°’ 뢈일치 λ˜λŠ” ν˜„μƒμ„ 미연에 λ°©μ§€ν•œλ‹€.

단, 직렬화 μŠ€νŽ™ 상 serialVersionUID κ°’ λͺ…μ‹œλŠ” ν•„μˆ˜κ°€ μ•„λ‹ˆλ©°, 만일 ν΄λž˜μŠ€μ— SUID ν•„λ“œλ₯Ό λͺ…μ‹œν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄, μ‹œμŠ€ν…œμ΄ λŸ°νƒ€μž„μ— ν΄λž˜μŠ€μ˜ 이름, μƒμ„±μž λ“±κ³Ό 같이 클래슀의 ꡬ쑰λ₯Ό μ΄μš©ν•΄ μ•”ν˜Έ ν•΄μ‹œν•¨μˆ˜λ₯Ό μ μš©ν•΄ μžλ™μœΌλ‘œ 클래슀 μ•ˆμ— μƒμ„±ν•˜κ²Œ λœλ‹€.

 

예λ₯Όλ“€μ–΄ λ‹€μŒ Member 클래슀λ₯Ό μ§λ ¬ν™”μ‹œμΌœ Member.ser 파일둜 μ €μž₯ν•˜κ³  μ„œλΉ„μŠ€μ—μ„œ 이λ₯Ό 가져와 μ—­μ§λ ¬ν™”ν•˜μ—¬ μ‚¬μš©ν•œλ‹€κ³  κ°€μ •ν•΄λ³΄μž.

class Member implements Serializable {
    private String name;
    private int age;
    private String address;

    ...
}

SerialVersionUID

그런데 Member ν΄λž˜μŠ€μ— 이메일 ν•„λ“œλ₯Ό μΆ”κ°€ν•΄μ•Ό ν•œλ‹€λŠ” λͺ…μ„Έμ„œκ°€ μ™”λ‹€. κ·Έλž˜μ„œ Member ν΄λž˜μŠ€μ— email μΈμŠ€ν„΄μŠ€ ν•„λ“œλ₯Ό μΆ”κ°€ν•˜μ˜€κ³  ν‰μ†Œμ²˜λŸΌ ν”„λ‘œκ·Έλž¨μ„ μ‹€ν–‰μ‹œμΌ°λ‹€.

class Member implements Serializable {
    private String name;
    private int age;
    private String address;
    
    private String email; // μƒˆλ‘œ μΆ”κ°€ν•œ 클래슀 ꡬ성 μš”μ†Œ

    ...
}

SerialVersionUID

κ·ΈλŸ¬λ‚˜ κ²°κ³ΌλŠ” μœ„μ™€ 같은 μ‹œλ»˜κ±΄ μ—λŸ¬κ°€ 우리λ₯Ό λ°˜κ²¨μ€„ 것이닀.

μ•žμ„  μ˜ˆμ œμ—μ„œλ„ 직렬화 클래슀λ₯Ό μ„ μ–Έν•  λ•Œ SUID 값을 μƒλž΅ν–ˆμ§€λ§Œ λ‚΄λΆ€μ μœΌλ‘œ μ‹λ³„λ²ˆν˜Έκ°€ μƒμ„±λ˜μ–΄ μžˆμ–΄ λ‚˜μ€‘에 클래슀λ₯Ό μˆ˜μ •ν•˜κ²Œ λœλ‹€λ©΄ SUID κ°’도 λ³€ν•˜κ²Œ λ˜μ–΄ μ—­μ§λ ¬ν™”μ‹œ 였λ₯˜κ°€ μƒκΈ°λŠ” 것이닀.


클래슀 버전 μˆ˜λ™ 관리

만일 λ„€νŠΈμ›Œν¬λ‘œ 객체λ₯Ό μ§λ ¬ν™”ν•˜μ—¬ μ „μ†‘ν•˜κ±°λ‚˜ ν˜‘μ—…μ„ ν•˜λŠ” 경우 μˆ˜μ‹ μžμ™€ μ†‘μ‹ μž λͺ¨λ‘ 같은 λ²„μ „μ˜ 클래슀λ₯Ό 가지고 μžˆμ–΄μ•Ό 할텐데, 만일 ν΄λž˜μŠ€κ°€ 쑰금만 변경사항이 있으면 λͺ¨λ“  μ‚¬μš©μžμ—κ²Œ μž¬λ°°ν¬ν•΄μ•Ό ν•˜λŠ” μ• λ‘œμ‚¬ν•­μ΄ 생겨 ν”„λ‘œκ·Έλž¨μ„ κ΄€λ¦¬ν•˜κΈ° μ–΄λ ΅κ²Œ λ§Œλ“ λ‹€.

λ”°λΌμ„œ 직렬화 ν΄λž˜μŠ€λŠ” μ™ λ§Œν•œ 상황에선 serialVersionUID λ₯Ό 직접 λͺ…μ‹œν•΄μ£Όμ–΄ 클래슀 버전을 μˆ˜λ™μœΌλ‘œ κ΄€λ¦¬ν•˜λŠ” 것을 ꢌμž₯ν•˜λŠ” νŽΈμ΄λ‹€. SUIDλ₯Ό 직접 λͺ…μ‹œν•΄μ£Όλ©΄ 클래슀의 λ‚΄μš©μ΄ λ³€κ²½λ˜μ–΄λ„, 클래슀의 버전이 μ‹œμŠ€ν…œμ΄ μžλ™ μƒμ„±λœ κ°’μœΌλ‘œ λ³€κ²½λ˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ΄λ‹€. 이외에도 λŸ°νƒ€μž„μ— SUIDλ₯Ό μƒμ„±ν•˜λŠ” μ‹œκ°„λ„ 많이 μž‘μ•„λ¨ΉκΈ° λ•Œλ¬Έμ— 미리 λͺ…μ‹œλ₯Ό κ°•λ ₯히 ꢌμž₯λ˜λŠ” 바이닀.

serialVersionUID λŠ” μ•„λž˜μ™€ 같이 private static final μ œμ–΄μžλ‘œ μ„ μ–Έν•΄μ•Ό ν•˜λ©° νƒ€μž…μ€ long 이닀.

class Member implements Serializable {
    // serialVersionUID κΌ­ λͺ…μ‹œ ν•  것
    private static final long serialVersionUID = 123L;
    
    private String name;
    private int age;
    private String address;
    
    // private String email; // μƒˆλ‘œ μΆ”κ°€ν•œ 클래슀 ꡬ성 μš”μ†Œ

    ...
}

이제 SUIDλ₯Ό μ„ μ–Έν•œ Member 클래슀λ₯Ό μ§λ ¬ν™”ν•˜μ—¬ μ™ΈλΆ€ 파일둜 μΆ”μΆœν•˜κ³ , email ν•„λ“œλ₯Ό ν΄λž˜μŠ€μ— μƒˆλ‘œ μΆ”κ°€ν•˜κ³  역직렬화 ν•΄λ³΄μž. 그러면 email ν•„λ“œλŠ” μ•Œμ•„μ„œ μ œμ™Έ(null둜 μ΄ˆκΈ°ν™”)되고 역직렬화됨을 λ³Ό 수 μžˆλ‹€.

SerialVersionUID

μ΄λ ‡κ²Œ 클래슀 내에 serialVersionUIDλ₯Ό μ •μ˜ν•΄μ£Όλ©΄, 클래슀의 λ‚΄μš©μ΄ λ°”λ€Œμ–΄λ„ 클래슀의 버전이 μœ μ§€λ¨μœΌλ‘œ, 비둝 ν•„λ“œκ°€ λ§€μΉ­λ˜μ§€ μ•Šλ”λΌλ„ 일단은 역직렬화 λ™μž‘ μžμ²΄λŠ” ν–‰ν•˜λ„λ‘ ν•  수 μžˆλ‹€. 

 

SerialVersionUID μžλ™ μƒμ„±ν•˜κΈ°

serialVersionUIDλŠ” μ •μˆ˜κ°’μ΄λΌ μ–΄λ– ν•œ κ°’μœΌλ‘œλ„ 지정할 수 μžˆμ§€λ§Œ, λ‹¨μˆœν•œ 값이면 κ²ΉμΉ  μš°λ €κ°€ 있기 λ•Œλ¬Έμ— μ„œλ‘œ λ‹€λ₯Έ ν΄λž˜μŠ€κ°„μ— 같은 값을 갖지 μ•Šλ„λ‘ serialversion 값을 μƒμ„±ν•΄μ£ΌλŠ” ν”„λ‘œκ·Έλž¨μ„ μ‚¬μš©ν•˜λŠ” 것이 μ’‹λ‹€.

JVM을 μ„€μΉ˜ν• λ•Œ 같이 μ„€μΉ˜λ˜λŠ” serialver.exeλ₯Ό μ‚¬μš©ν•΄μ„œ μƒμ„±λœ 값을 μ΄μš©ν•  수 μžˆμ§€λ§Œ λ²ˆκ±°λ‘œμš°λ―€λ‘œ, IntelliJλ₯Ό μ΄μš©ν•΄ κ°„λ‹¨ν•œ μ„€μ •λ§ŒμœΌλ‘œ 클릭 ν•œλ²ˆ 만으둜 SUID 값을 생성해 쀄 수 μžˆλŠ” 방법을 μ†Œκ°œν•΄λ³Έλ‹€.

 

SerialVersionUID μˆ˜λ™ 관리 μœ μ˜μ‚¬ν•­

클래슀 serialVersionUIDλ₯Ό λͺ…μ‹œν•˜λ”λΌλ„ μ ˆλŒ€ 만λŠ₯이 μ•„λ‹ˆλ‹€. μœ„μ™€ 같이 λ‹¨μˆœνžˆ ν•„λ“œ λ³€μˆ˜ ν•˜λ‚˜ μΆ”κ°€ν•˜λŠ” μ •λ„λŠ” λ¬Έμ œκ°€ μ—†κ² μ§€λ§Œ ν•„λ“œ νƒ€μž…μ„ λ³€κ²½ν•˜λŠ” μƒν™©μ—μ„œλŠ” 버전 μˆ˜λ™ 관리λ₯Ό ν•˜μ—¬λ„ μ˜ˆμ™Έλ₯Ό λ§‰μ„μˆœ μ—†λ‹€.

예λ₯Όλ“€μ–΄ Member 클래슀의 ageλ₯Ό int ν˜• μ—μ„œ long ν˜•μœΌλ‘œ μ—…λ°μ΄νŠΈν•˜κ³  역직렬화λ₯Ό 해보면 μ•„λž˜μ™€ 같이 incompatible type μ—λŸ¬κ°€ λ°œμƒν•˜κ²Œ λœλ‹€.

SerialVersionUID
SerialVersionUID

μžλ°”μ—μ„œ 직렬화λ₯Ό μ‚¬μš©ν•  λ•Œ μ˜ˆμ™Έκ°€ λ°œμƒν•˜κ±°λ‚˜ 주의 ν•΄μ•Ό ν•˜λŠ” 상황을 정리해보면 λ‹€μŒκ³Ό κ°™λ‹€.

  1. 멀버 λ³€μˆ˜λ₯Ό μΆ”κ°€ν•  λ•Œ (영ν–₯ μ—†μŒ - κΈ°λ³Έκ°’μœΌλ‘œ μ„€μ •)
  2. 멀버 λ³€μˆ˜κ°€ μ‚­μ œλ  λ•Œ  (영ν–₯ μ—†μŒ)
  3. 멀버 λ³€μˆ˜μ˜ 이름이 λ°”λ€” λ•Œ (영ν–₯ μ—†μŒ - 값이 ν• λ‹Ήλ˜μ§€ μ•ŠμŒ)
  4. 멀버 λ³€μˆ˜μ˜ μ ‘κ·Ό μ œμ–΄μž λ³€κ²½ (영ν–₯ μ—†μŒ)
  5. 멀버 λ³€μˆ˜μ˜ νƒ€μž…μ΄ λ°”λ€” λ•Œ  (영ν–₯ 있음)
  6. 멀버 λ³€μˆ˜μ— static 와 transient μΆ”κ°€  (영ν–₯ μ—†μŒ)

μœ„μ˜ μ£Όμ˜μ‚¬ν•­μ„ 보면 ν•„λ“œ νƒ€μž… λ³€κ²½λ§Œ μ‘°μ‹¬ν•˜λ©΄ 될것 κ°™μ§€λ§Œ, 사싀 직렬화λ₯Ό μ‚¬μš©ν•  λ•ŒλŠ” μžμ£Ό 변경될 μ†Œμ§€κ°€ μžˆλŠ” 클래슀의 κ°μ²΄λŠ” κ·Έλƒ₯ 직렬화λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” 것이 μ’‹λ‹€. μΆ”ν›„ λ²„μ „μ—μ„œ 이전 버전에 영ν–₯없이 μ†ŒμŠ€ μ½”λ“œλ₯Ό μˆ˜μ •ν•˜λŠ” 것은 맀우 μ–΄λ ΅κΈ° λ•Œλ¬Έμ΄λ‹€.


μžλ°” 직렬화 μ˜ˆμ™Έ

 

InvalidClassException

InvalidClassException μ˜ˆμ™ΈλŠ” 직렬화λ₯Ό μ΄μš©ν•˜λ‹€λ³΄λ©΄ ν”νžˆ μ ‘ν•  수 μžˆλŠ” 였λ₯˜μ΄λ‹€. 이 μ˜ˆμ™ΈλŠ” μ•„λž˜ 3가지 μ£Όμš” μ›μΈμœΌλ‘œ 인해  Serialized λ˜λŠ” Deserialized ν•  수 μ—†λ‹€λŠ” 것을 μ˜λ―Έν•œλ‹€.

  1. 클래슀의 SerialVersionUID 버전이 λ‹€λ₯Έ 경우
  2. ν΄λž˜μŠ€μ— λ‹€λ₯Έ 데이터 νƒ€μž…μ„ ν¬ν•¨ν•œ 경우
  3. κΈ°λ³Έ μƒμ„±μžκ°€ μ—†λŠ” 경우

이쀑 1번과 2λ²ˆμ€ 이미 μœ„μ—μ„œ 닀룬바 μžˆλ‹€. κ·Έλž˜μ„œ μ΄λ²ˆμ—λŠ” 3번의 λ””ν΄νŠΈ μƒμ„±μžκ°€ μ—†λŠ” 경우λ₯Ό μ‚΄νŽ΄λ³΄μž.

 

no valid constructor

InvalidClassException 쀑 μƒμ„±μžκ°€ μ—†λ‹€λŠ” μ˜ˆμ™ΈλŠ” 클래슀 상속 κ΄€κ³„μ—μ„œμ˜ 직렬화λ₯Ό ν–‰ν•˜μ˜€μ„λ•Œ λ°œμƒν•œλ‹€.

클래슀 상속 관계 κ΅¬μ‘°μ—μ„œμ˜ μ§λ ¬ν™”λŠ” μžμ‹ ν΄λž˜μŠ€λΆ€ν„° μ§λ ¬ν™”ν•œ λ’€ λΆ€λͺ¨ 클래슀둜 μ΄λ™ν•΄μ„œ 직렬화 ν•˜κ²Œ λœλ‹€. 그리고 객체λ₯Ό μ—­μ§λ ¬ν™”ν• λ•Œ λ°˜λŒ€λ‘œ λΆ€λͺ¨ 클래슀 λΆ€ν„° μ‹œμž‘ν•΄μ„œ 상속 ꡬ쑰λ₯Ό 따라 λ‚΄λ €κ°€κ²Œ λœλ‹€.

그런데 만일 λΆ€λͺ¨ ν΄λž˜μŠ€κ°€ non-serializable(직렬화λ₯Ό κ΅¬ν˜„ν•˜μ§€ μ•Šμ€) ν•œλ‹€λ©΄ μ—­μ§λ ¬ν™”ν•˜λŠ” κ³Όμ •μ—μ„œ μ§λ ¬ν™”λ˜μ§€ μ•Šμ€ λΆ€λͺ¨μ˜ 속성 정보듀을 κΈ°λ³Έ μƒμ„±μžλ₯Ό ν†΅ν•΄μ„œ κ°€μ Έμ˜€κ²Œ λœλ‹€. 그런데 만일 κΈ°λ³Έ μƒμ„±μžκ°€ μ—†λ‹€λ©΄ 뢈러올 μœ νš¨ν•œ μƒμ„±μž(vaild constructor)κ°€ μ—†μ–΄μ„œ 였λ₯˜κ°€ λ‚˜λŠ” 것이닀.

 

예λ₯Ό λ“€μ–΄ UserInfo의 λΆ€λͺ¨ 클래슀인 UserAccountκ°€ name κ³Ό passwordλ₯Ό λ°›λŠ” μ»€μŠ€ν…€ μƒμ„±μžλ₯Ό μ •μ˜ν•˜κ²Œ 되면, 기쑴의 κΈ°λ³Έ μƒμ„±μžλŠ” μ‚¬λΌμ§€κ²Œ λœλ‹€. 이 μƒνƒœμ—μ„œ 직렬화 및 역직렬화λ₯Ό μˆ˜ν–‰ν•˜κ²Œ 되면 no valid constructor μ˜ˆμ™Έκ°€ λ°œμƒλ˜κ²Œ λœλ‹€.

class UserAccount {
    String name;
    String password;

    // ! κΈ°λ³Έ μƒμ„±μž μ—†μœΌλ©΄ InvalidClassException : no valid constructor λ°œμƒ
    // public UserAccount() {}

    UserAccount(String name, String password) {
        this.name = name;
        this.password = password;
    }
}
class UserInfo extends UserAccount implements Serializable {
    int age;
    int height;
    boolean marreid;

    UserInfo(String name, String password, int age, int height, boolean marreid) {
        super(name, password);
        this.age = age;
        this.height = height;
        this.marreid = marreid;
    }

    ...
}

InvalidClassException

λ”°λΌμ„œ 상속 κ΄€κ³„μ—μ„œμ˜ 직렬화λ₯Ό ν–‰ν• λ•ŒλŠ” non-serializable ν΄λž˜μŠ€μ— λŒ€ν•΄μ„œ λ°˜λ“œμ‹œ κΈ°λ³Έ μƒμ„±μžλ₯Ό μΌλΆ€λ‘œ μ •μ˜λ₯Ό ν•΄μ•Ό λœλ‹€λŠ” 점을 μžŠμ§€ 말아야 λœλ‹€.


NotSerializableException

λ˜ν•œ μ£Όμ˜ν•΄μ•Ό ν•  사항은 ν΄λž˜μŠ€λŠ” Serializable을 κ΅¬ν˜„ν•˜κ³  μžˆμ§€λ§Œ λ‹€λ₯Έ 클래슀의 객체λ₯Ό 멀버 λ³€μˆ˜λ‘œ 가지고 μžˆμ„ 경우, 그리고 κ·Έ μ°Έμ‘°ν•˜κ³  μžˆλŠ” ν΄λž˜μŠ€κ°€ Serializable을 κ΅¬ν˜„ν•˜μ§€ μ•Šμ•˜μ„ κ²½μš°μ΄λ‹€.

예λ₯Όλ“€μ–΄ μ•„λž˜μ™€ 같이 Object 객체λ₯Ό ν•„λ“œλ‘œ μ°Έμ‘°ν•˜κ³  μžˆμ„ 경우, ObjectλŠ” Serializable을 κ΅¬ν˜„ν•˜μ§€ μ•Šμ•˜κΈ° λ•Œλ¬Έμ— NotSerializableException이 λ°œμƒν•˜λ©΄μ„œ 직렬화에 μ‹€νŒ¨ν•˜κ²Œ λœλ‹€.

class UserInfo implements Serializable {
    String name;
    int age;
    boolean marreid;
    
    Object obj;
}

NotSerializableException

만일 Objectκ°€ Serializable을 κ΅¬ν˜„ν–ˆλ‹€λ©΄ λͺ¨λ“  ν΄λž˜μŠ€κ°€ 직렬화 될 수 μžˆμ„ 것이기 λ•Œλ¬Έμ— 졜고 쑰상인 ObjectλŠ” 기본적으둜 직렬화λ₯Ό κ΅¬ν˜„ν•˜κ³  μžˆμ§€ μ•Šλ‹€.

λ”°λΌμ„œ Serializableλ₯Ό κ΅¬ν˜„ν•œ 클래슀 내뢀에 직렬화 ν•˜μ§€μ•Šμ€ λ‹€λ₯Έ 객체λ₯Ό μ°Έμ‘°ν•˜κ³  μžˆλŠ” ν•„λ“œκ°€ μžˆμ„ 경우 ν•΄λ‹Ή ν΄λž˜μŠ€λ„ 직렬화가 κ°€λŠ₯ν•˜λ„λ‘ Serializable μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜λ„λ‘ μ„€μ •ν•˜κ±°λ‚˜ transient ν‚€μ›Œλ“œλ₯Ό 톡해 직렬화 μ œμ™Έ 섀정을 ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€.

μΆ”κ°€λ‘œ String νƒ€μž…λ„ μΌμ’…μ˜ 클래슀인데 직렬화가 κ°€λŠ₯ν•œ μ΄μœ λŠ”, String 클래슀 λͺ…μ„Έλ₯Ό 보면 Serializable μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜κ³  있기 λ•Œλ¬Έμ΄λ‹€.

NotSerializableException


μžλ°” 직렬화 문제점

이 νŒŒνŠΈλŠ” μžλ°”(Java) 개발자라면 ν•œλ²ˆμ―€ 읽어봐야 ν•˜λŠ” μ‘°μŠˆμ•„ λΈ”λ‘œν¬(Joshua Bloch)의 μ €μ„œ μ΄νŽ™ν‹°λΈŒ μžλ°” 3/E 판의 직렬화 뢀뢄을 λ¨ΉκΈ° μ’‹κ²Œ(κΈ°μ–΅ν•˜κΈ° μ’‹κ²Œ) μš”μ•½ κ΅¬μ„±ν•œ 글이닀. ✍️

 

Serializable μΈν„°νŽ˜μ΄μŠ€λ₯Ό ν΄λž˜μŠ€μ— implements ν•˜λ©΄ μ•„μ£Ό κ°„λ‹¨ν•˜κ²Œ 직렬화 κ°€λŠ₯ν•œ ν΄λž˜μŠ€κ°€ λ˜μ–΄ μ™ΈλΆ€λ‘œ 내보낼 수 μžˆλ‹€. κ·ΈλŸ¬λ‚˜ κ°„λ‹¨ν•œ μ„ μ–Έ 방법과 λ‹€λ₯΄κ²Œ 직렬화 κ΅¬ν˜„ λŒ€κ°€λŠ” 맀우 λΉ„μ‹Έλ‹€. 직렬화λ₯Ό κ΅¬ν˜„ν•œ μˆœκ°„λΆ€ν„° λ§Žμ€ μœ„ν—˜μ„±μ„ κ°–κ²Œ 되기 λ•Œλ¬Έμ΄λ‹€. 이에 λŒ€ν•΄μ„œ μžμ„Ένžˆ μ•Œμ•„λ³΄λ„λ‘ ν•˜μž.


μžλ°” μ§λ ¬ν™”μ˜ λŒ€μ•ˆμ„ μ°ΎμœΌλΌ

ν¬μŠ€νŒ… μ΄ˆλ°˜μ— JSON과의 비ꡐλ₯Ό 톡해 μ§λ ¬ν™”μ˜ μž₯점에 λŒ€ν•΄ μ†Œκ°œλ₯Ό ν•˜μ˜€μ§€λ§Œ, 사싀 μ§λ ¬ν™”λŠ” μž₯점보닀 단점이 κ·Ήλͺ…ν•˜κ²Œ λ§Žλ‹€. 

 

1. μ§λ ¬ν™”λŠ” μš©λŸ‰μ΄ 크닀

μ§λ ¬ν™”λŠ” 객체에 μ €μž₯된 데이터값 뿐만 μ•„λ‹ˆλΌ νƒ€μž… 정보, 클래슀 메타 정보λ₯Ό 가지고 μžˆμœΌλ―€λ‘œ μš©λŸ‰μ„ μ€κ·Όνžˆ 많이 μ°¨μ§€ν•œλ‹€. κ·Έλž˜μ„œ 같은 정보λ₯Ό μ§λ ¬ν™”λ‘œ μ €μž₯ν•˜λŠλƒ JSON으둜 μ €μž₯ν•˜λŠλƒλŠ” 파일 μš©λŸ‰ 크기가 거의 2λ°° 이상 차이가 λ‚œλ‹€.

λ”°λΌμ„œ DB, Cache 등에 외뢀에 μ €μž₯ν• λ•Œ, μž₯κΈ°κ°„ λ™μ•ˆ μ €μž₯ν•˜λŠ” μ •λ³΄λŠ” 직렬화λ₯Ό 지양해야 λœλ‹€.

 

2. μ—­μ§λ ¬ν™”λŠ” μœ„ν—˜ν•˜λ‹€

κ²°λ‘ λΆ€ν„° λ§ν•˜μžλ©΄ 직렬화 μ„€μ • μžμ²΄λŠ” λ¬Έμ œλŠ” μ—†μ§€λ§Œ, 남이 λ§Œλ“  것을 역직렬화 κ³Όμ •μ—μ„œ λ‚˜λ„ λͺ¨λ₯΄κ²Œ 곡격당할 μœ„ν—˜μ„±μ΄ μžˆλ‹€.

역직렬화 κ³Όμ •μ—μ„œ ν˜ΈμΆœλ˜μ–΄ 잠재적으둜 μœ„ν—˜ν•œ λ™μž‘μ„ μˆ˜ν–‰ν•˜λŠ” λ©”μ„œλ“œλ₯Ό κ°€μ ―(gadget) 이라고 λΆ€λ₯΄λŠ”데, λ°”μ΄νŠΈ μŠ€νŠΈλ¦Όμ„ μ—­μ§λ ¬ν™”ν•˜λŠ” ObjectInputStream의 readObject() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜κ²Œ 되면 객체 κ·Έλž˜ν”„κ°€ μ—­μ§λ ¬ν™”λ˜μ–΄ classpath μ•ˆμ˜ λͺ¨λ“  νƒ€μž…μ˜ 객체λ₯Ό λ§Œλ“€μ–΄ λ‚΄κ²Œ λ˜λŠ”λ°, ν•΄λ‹Ή νƒ€μž… κ°μ²΄μ•ˆμ˜ λͺ¨λ“  μ½”λ“œλ₯Ό μˆ˜ν–‰ν• μˆ˜ 있게 λ˜λ―€λ‘œ λ‚˜μ˜ ν”„λ‘œκ·Έλž¨ μ½”λ“œ 전체가 곡격 λ²”μœ„μ— λ“€μ–΄κ°€κ²Œ λœλ‹€.

λ˜λŠ” 객체λ₯Ό μ§λ ¬ν™”ν•˜μ—¬ μ™ΈλΆ€λ‘œ μ „μ†‘ν•˜λŠ” κ³Όμ •μ—μ„œ 쀑간에 λˆ„κ°€ κ°€λ‘œμ±„ 파일 λ°”μ΄νŠΈ λ‚΄μš©μ„ μ‘°μž‘ν•˜μ—¬, μ†‘μ‹ μžκ°€ μ—­μ§λ ¬ν™”ν•˜λŠ” κ³Όμ •μ—μ„œ μΈμŠ€ν„΄μŠ€μ— μœ„ν—˜ν•œ 값을 λŒ€μž…μ‹œμΌœ λΆˆλ³€μ„ κΉ¨λŠ” μ‹μœΌλ‘œμ˜ 곡격도 κ°€λŠ₯ν•˜λ‹€. μ™œλƒν•˜λ©΄ μ—­μ§λ ¬ν™”λŠ” μƒμ„±μž 없이 μΈμŠ€ν„΄μŠ€ν™”κ°€ κ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ— 보이지 μ•ŠλŠ” μƒμ„±μž 라고도 λΆˆλ¦¬μš΄λ‹€.

μ—­μ§λ ¬ν™”λŠ” μœ„ν—˜ν•˜λ‹€
https://www.cobalt.io/blog/the-anatomy-of-deserialization-attacks

λ”°λΌμ„œ μ‹ λ’°ν•  수 μ—†λŠ” λ°μ΄ν„°λŠ” μ ˆλŒ€ 역직렬화 ν•˜λ©΄ μ•ˆλ˜λ©°, μ§λ ¬ν™”μ˜ 잠재적인 μœ„ν—˜μ„±μ„ νšŒν”Όν•˜λŠ” κ°€μž₯ 쒋은 방법은 아무것도 μ—­μ§λ ¬ν™”ν•˜μ§€ μ•ŠλŠ” 것이닀. 

 

3. 직렬화λ₯Ό λŒ€μ²΄ ν•  수 μ—†λ‹€λ©΄

만일 Serializable을 κ΅¬ν˜„ν•œ 클래슀만 μ§€μ›ν•˜λŠ” ν”„λ ˆμž„μ›Œν¬λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•˜κ±°λ‚˜ λ ˆκ±°μ‹œ μ‹œμŠ€ν…œ λ•Œλ¬Έμ— μ–΄μ©”μˆ˜ 없이 ν΄λž˜μŠ€μ— 직렬화λ₯Ό μ„€μ •ν•΄μ•Ό ν•  경우, 직렬화λ₯Ό ν”Όν•  수 μ—†λ‹€λ©΄ 역직렬화 필터링(ObjectInputFilter)κ³Ό 같은 역직렬화 λ°©μ–΄ 기법듀을 μ‚¬μš©ν•˜λ©΄ λœλ‹€. 데이터 슀트림이 μ—­μ§λ ¬ν™”λ˜κΈ° 전에 ν•„ν„° 쑰건문을 μˆ˜ν–‰ν•˜μ—¬ νŠΉμ • 클래슀만 ν—ˆμš©ν•˜κ±°λ‚˜ μ œμ™Έν•˜λ„λ‘ ν•  수 μžˆλ‹€.

 

[JAVA] β˜• 객체 역직렬화 λ°©μ–΄ 기법 - 총정리 λͺ¨μŒ

Serializable κ΅¬ν˜„μ€ λ³΄μ•ˆμ— ꡬ멍이 생길 수 μžˆλ‹€ 보톡 μžλ°”μ—μ„œ μΈμŠ€ν„΄μŠ€λŠ” μƒμ„±μžλ₯Ό μ΄μš©ν•΄ λ§Œλ“ λŠ” 것이 기본이닀. ν•˜μ§€λ§Œ μ—­μ§λ ¬ν™”λŠ” μ–Έμ–΄μ˜ κΈ°λ³Έ λ©”μ»€λ‹ˆμ¦˜μ„ μš°νšŒν•˜μ—¬ 객체λ₯Ό λ°”λ‘œ μƒμ„±ν•˜λ„λ‘

inpa.tistory.com

κ·ΈλŸ¬λ‚˜ κ΄€λ ¨λœ λͺ¨λ²” 사둀λ₯Ό λ”°λΌμ„œ 직렬화 κ°€λŠ₯ ν΄λž˜μŠ€λ“€μ„ 곡격에 λŒ€λΉ„ν•˜λ„λ‘ μž‘μ„±ν•œλ‹€ 해도 μ—¬μ „νžˆ μ·¨μ•½ν•˜κΈ° λ•Œλ¬Έμ— κ°€μž₯ ν™•μ‹€ν•œ 방법은 역직렬화λ₯Ό μ•ˆν•˜λŠ” 것이닀.


직렬화λ₯Ό κ΅¬ν˜„ν• μ§€λŠ” μ‹ μ€‘νžˆ κ²°μ •ν•˜λΌ

 

1. 릴리즈 후에 μˆ˜μ •μ΄ μ–΄λ ΅λ‹€

ν΄λž˜μŠ€κ°€ Serializable을 κ΅¬ν˜„ν•˜κ²Œ 되면 μ§λ ¬ν™”λœ λ°”μ΄νŠΈ 슀트림 인코딩도 ν•˜λ‚˜μ˜ 곡개 APIκ°€ λ˜λŠ” 것이닀. κ·Έλž˜μ„œ 직렬화을 κ΅¬ν˜„ν•œ ν΄λž˜μŠ€κ°€ 널리 퍼지면 κ·Έ 직렬화 ν˜•νƒœλ„ μ˜μ›νžˆ μ§€μ›ν•΄μ•Όν•œλ‹€. 클래슀의 λ‚΄λΆ€ κ΅¬ν˜„μ„ μˆ˜μ •ν•œλ‹€λ©΄ μ›λž˜μ˜ 직렬화 ν˜•νƒœμ™€ λ‹¬λΌμ§€κ²Œ 되기 λ•Œλ¬Έμ΄λ‹€.

즉, Serializable을 κ΅¬ν˜„ν•œ μˆœκ°„λΆ€ν„° ν•΄λ‹Ή 객체의 μœ μ§€λ³΄μˆ˜λŠ” 직렬화에 묢이게 λ˜λŠ” 것이닀. 

 

2. 클래슀 μΊ‘μŠν™”κ°€ 깨진닀

만일 직렬화할 ν΄λž˜μŠ€μ— private 멀버가 μžˆμ–΄λ„ 직렬화λ₯Ό ν•˜κ²Œ 되면 κ·ΈλŒ€λ‘œ μ™ΈλΆ€λ‘œ λ…ΈμΆœλ˜κ²Œ λœλ‹€. (직렬화λ₯Ό μ œμ™Έν•˜λ €λ©΄ λ³„λ„λ‘œ transient μ„€μ •ν•΄μ•Ό λœλ‹€)

λ”°λΌμ„œ Serializable을 κ΅¬ν˜„ν•˜λ©΄ 직렬화 ν˜•νƒœκ°€ ν•˜λ‚˜μ˜ 곡개 APIκ°€ λ˜μ–΄ μΊ‘μŠν™”κ°€ κΉ¨μ§€κ²Œ λœλ‹€.

 

3. 버그와 λ³΄μ•ˆμ— μ·¨μ•½ν•˜λ‹€

μžλ°”μ—μ„œλŠ” 객체λ₯Ό μƒμ„±μžλ₯Ό μ΄μš©ν•΄ λ§Œλ“ λŠ” 것이 기본이닀. ν•˜μ§€λ§Œ μ—­μ§λ ¬ν™”λŠ” μ–Έμ–΄μ˜ κΈ°λ³Έ λ©”μ»€λ‹ˆμ¦˜μ„ μš°νšŒν•˜μ—¬ 객체λ₯Ό λ°”λ‘œ μƒμ„±ν•˜λ„λ‘ ν•œλ‹€. 즉, μ—­μ§λ ¬ν™”λŠ” μˆ¨μ€ μƒμ„±μž 이기도 ν•œ 것이닀.

λ¬Έμ œλŠ” 만일 μ–΄λŠ 객체가 μƒμ„±μžλ₯Ό 톡해 μΈμŠ€ν„΄μŠ€ν™” ν• λ•Œ λΆˆλ³€μ‹μ΄λ‚˜ ν—ˆκ°€λ˜μ§€ μ•Šμ€ 접근을 μ„€μ •ν•˜μ˜€μ„ 경우 이λ₯Ό λ¬΄μ‹œν•˜κ³  μƒμ„±λœλ‹€λŠ” 점이닀.

예λ₯Όλ“€μ–΄ μ•„λž˜ Member ν΄λž˜μŠ€λŠ” μƒμ„±μž μž…λ ₯κ°’μœΌλ‘œ μ΄μƒν•œ 값을 넣을 경우 이λ₯Ό κ±ΈλŸ¬λ‚΄λŠ” 둜직이 μžˆλŠ”λ°, Member 객체λ₯Ό μ§λ ¬ν™”ν•˜κ³  역직렬화할 경우 age에 μŒμˆ˜κ°’μ΄ 듀어가도 이λ₯Ό κ±ΈλŸ¬λ‚Όμˆ˜ μ—†λŠ” 것이닀. (이 뢀뢄은 μœ„μ—μ„œ μ†Œκ°œν•œ 역직렬화 λ°©μ–΄ 기법 쀑 ν•˜λ‚˜μΈ 직렬화 ν”„λ‘μ‹œ νŒ¨ν„΄μœΌλ‘œ 극볡은 ν•  수 μžˆλ‹€)

class Member implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

    public Member(String name, int age) {
        if(age < 0){
            throw new IllegalArgumentException();
        }
        this.name = name;
        this.age = age;
    }
}

 

4. μƒˆλ‘œμš΄ 버전을 λ¦΄λ¦¬μ¦ˆν•  λ•Œ ν…ŒμŠ€νŠΈ μš”μ†Œκ°€ λ§Žμ•„μ§„λ‹€

만일 직렬화 κ°€λŠ₯ν•œ ν΄λž˜μŠ€κ°€ μ—…λ°μ΄νŠΈλ˜λ©΄, κ΅¬λ²„μ „μ˜ 직렬화 ν˜•νƒœκ°€ μ‹ λ²„μ „μ—μ„œ 역직렬화가 κ°€λŠ₯ν•œμ§€ ν…ŒμŠ€νŠΈν•΄μ•Ό ν•  것이닀. 즉, ν…ŒμŠ€νŠΈμ˜ 양이 직렬화 κ°€λŠ₯ 클래슀의 μˆ˜μ™€ 릴리즈 νšŸμˆ˜μ— λΉ„λ‘€ν•˜κ²Œ λœλ‹€.

 

5. κ΅¬ν˜„ μ—¬λΆ€λŠ” μ‰½κ²Œ κ²°μ •ν•  것이 μ•„λ‹ˆλ‹€

Serializable을 κΌ­ κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€λ©΄ 클래슀λ₯Ό 섀계할 λ•Œλ§ˆλ‹€ λ”°λ₯΄λŠ” 이득과 λΉ„μš©μ„ 잘 κ³ λ €ν•΄μ•Ό ν•œλ‹€.

예λ₯Ό λ“€μ–΄ BigInteger와 Instant 같은 'κ°’' ν΄λž˜μŠ€μ™€ μ»¬λ ‰μ…˜ ν΄λž˜μŠ€λŠ” Serializable을 κ΅¬ν˜„ν•˜μ˜€κ³  μŠ€λ ˆλ“œ ν’€μ²˜λŸΌ 'λ™μž‘' ν•˜λŠ” 객체λ₯Ό ν‘œν˜„ν•œ ν΄λž˜μŠ€λŠ” λŒ€λΆ€λΆ„ κ΅¬ν˜„ν•˜μ§€ μ•Šμ•˜λ‹€.

 

6. μƒμ†μš© ν΄λž˜μŠ€μ™€ μΈν„°νŽ˜μ΄μŠ€μ— 직렬화 κ΅¬ν˜„μ— μ£Όμ˜ν•΄μ•Ό ν•œλ‹€

상속 λͺ©μ μœΌλ‘œ μ„€κ³„λœ ν΄λž˜μŠ€μ™€ μΈν„°νŽ˜μ΄μŠ€λ₯Ό Serializable을 κ΅¬ν˜„ν•œλ‹€λŠ” 것은, μœ„μ—μ„œ μ–ΈκΈ‰ν•œ μžλ°” μ§λ ¬ν™”μ˜ μœ„ν—˜μ„±μ„ κ³ λ₯΄λž€νžˆ ν•˜μœ„ ν΄λž˜μŠ€μ—κ²Œ μ „μ΄ν•˜κ²Œ λ˜λŠ” 것과 닀름 μ—†κΈ° λ•Œλ¬Έμ΄λ‹€.

κ·Έλ ‡μ§€λ§Œ 만일 ν΄λž˜μŠ€κ°€ 직렬화 및 ν™•μž₯이 λͺ¨λ‘ κ°€λŠ₯ν•˜λ‹€κ²Œ ν•˜κ³  μ‹Άλ‹€λ©΄ λͺ‡ 가지 μ£Όμ˜μ‚¬ν•­μ΄ μžˆλ‹€.

μΈμŠ€ν„΄μŠ€ ν•„λ“œμ˜ κ°’ 쀑에 λΆˆλ³€μ‹μ„ 보μž₯ν•΄μ•Όν•  게 μžˆλ‹€λ©΄ λ°˜λ“œμ‹œ ν•˜μœ„ ν΄λž˜μŠ€μ—μ„œ Object 클래슀의 finalize λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•˜μ§€ λͺ»ν•˜κ²Œ ν•΄μ•Όν•œλ‹€. finalizeλ₯Ό μž¬μ •μ˜ν•˜λ©΄μ„œ final ν‚€μ›Œλ“œλ₯Ό λΆ™μ—¬μ„œ μ„ μ–Έν•˜λ©΄ λœλ‹€.

그리고 μΈμŠ€ν„΄μŠ€ ν•„λ“œμ€‘ κΈ°λ³Έκ°’ intλŠ” 0, ObjectλŠ” null λ“±μœΌλ‘œ μ„€μ •λ˜λ©΄ μœ„λ°°λ˜λŠ” λΆˆλ³€μ‹μ΄ μžˆλ‹€λ©΄ readObjectNoData λ©”μ„œλ“œλ₯Ό λ°˜λ“œμ‹œ μΆ”κ°€ν•΄μ•Όν•œλ‹€.

 

7. λ‚΄λΆ€ ν΄λž˜μŠ€λŠ” 직렬화λ₯Ό κ΅¬ν˜„ν•˜λ©΄ μ•ˆλœλ‹€

λ‚΄λΆ€ 클래슀(inner class)의 직렬화 ν˜•νƒœλŠ” λΆˆλΆ„λͺ…ν•˜λ―€λ‘œ Serializable을 κ΅¬ν˜„ν•˜λ©΄ μ•ˆλœλ‹€.

단 정적 λ‚΄λΆ€ 클래슀(static inner class)λŠ” Serializable 을 κ΅¬ν˜„ν•΄λ„ 상관없닀.


이처럼 μ§λ ¬ν™”λŠ” λ§Žμ€ 단점과 μœ„ν—˜ μš”μ†Œκ°€ μ‘΄μž¬ν•œλ‹€.

κ·ΈλŸ¬λ‚˜ μ§λ ¬ν™”λŠ” 1997년에 νƒ„μƒν•˜μ—¬ μ—¬μ „νžˆ μžλ°” μƒνƒœκ³„ 곳곳에 쓰이고 μžˆλ‹€. κ·Έλž˜μ„œ 만일 μ–΄μ©”μˆ˜ 없이 Serializableλ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€λΌλ©΄, 그에 λ”°λ₯Έ λΉ„μš©μ΄ 적지 μ•ŠμœΌλ‹ˆ 클래슀λ₯Ό 섀계 ν• λ•Œλ§ˆλ‹€ 이득과 λΉ„μš©μ„ 잘 μ €μšΈμ§ˆν•΄μ•Ό ν•œλ‹€. ν•˜μ§€λ§Œ μ‹œκ°„κ³Ό λ…Έλ ₯을 λ“€μ—¬μ„œλΌλ„ JSON λ“±μœΌλ‘œ 데이터 ν‘œν˜„μœΌλ‘œ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ν•˜λŠ” 것을 μΆ”μ²œν•˜λŠ” 바이닀.


# 참고자료

μ΄νŽ™ν‹°λΈŒ μžλ°” Effective Java 3/E

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

https://www.benchresources.net/serialization-and-de-serialization-in-java/

https://www.studytonight.com/java-examples/java-serialization-and-deserialization 

https://youtu.be/3iypR-1Glm0 

https://www.scaler.com/topics/transient-keyword-in-java/