Language/Java

β˜• μžλ°” 역직렬화 λ°©μ–΄ 기법 - 총정리 λͺ¨μŒ

인파_ 2023. 2. 14. 09:19

Deserialization-Filters

Serializable κ΅¬ν˜„μ€ λ³΄μ•ˆμ— ꡬ멍이 생길 수 μžˆλ‹€

보톡 μžλ°”μ—μ„œ μΈμŠ€ν„΄μŠ€λŠ” μƒμ„±μžλ₯Ό μ΄μš©ν•΄ λ§Œλ“ λŠ” 것이 기본이닀. ν•˜μ§€λ§Œ μ—­μ§λ ¬ν™”λŠ” μ–Έμ–΄μ˜ κΈ°λ³Έ λ©”μ»€λ‹ˆμ¦˜μ„ μš°νšŒν•˜μ—¬ 객체λ₯Ό λ°”λ‘œ μƒμ„±ν•˜λ„λ‘ ν•œλ‹€. μ§λ ¬ν™”λœ νŒŒμΌμ΄λ‚˜ λ°μ΄ν„°λ§Œ μžˆλ‹€λ©΄ readObject() λ₯Ό 톡해 μƒμ„±μž 없이 κ³§λ°”λ‘œ μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€μˆ˜ 있기 λ•Œλ¬Έμ΄λ‹€.

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

 

예λ₯Όλ“€μ–΄ μ•„λž˜ Member ν΄λž˜μŠ€λŠ” μƒμ„±μžλ‘œ λ‚˜μ΄ μž…λ ₯값을 음수λ₯Ό λ„£μœΌλ©΄ 이λ₯Ό κ±ΈλŸ¬λ‚΄λŠ” 둜직이 μžˆλ‹€κ³  ν•œλ‹€.

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;
    }

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

μ•„λž˜ μ²¨λΆ€νŒŒμΌμ€ μ˜ˆμ „μ— λˆ„κ΅°κ°€κ°€ Member 객체λ₯Ό μ§λ ¬ν™”ν•˜μ—¬ μ €μž₯ν•œ 파일이라고 ν•œλ‹€. 그런데 λˆ„κ΅°κ°€ 이λ₯Ό κ°€λ‘œμ±„μ–΄ λͺ°λž˜ 값을 μ‘°μž‘ν–ˆλ‹€.

Student.ser
0.00MB

우리 νŒ€μ€ κ·Έ 사싀을 λͺ¨λ₯΄κ³  이λ₯Ό 역직렬화 ν•˜μ—¬ ν”„λ‘œκ·Έλž¨μ„ μ‹€ν–‰ν•˜μ˜€λ‹€.

public static void main(String[] args) throws IOException, ClassNotFoundException {
    // 역직렬화
    ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream("Student.ser")));
    Member member = (Member) in.readObject();
    in.close();

    System.out.println(member);
}

serialize-readObject

이처럼 -100 μ΄λΌλŠ” μ˜³μ§€ μ•ŠλŠ” κ°’μœΌλ‘œ μ‘°μž‘λ˜μ–΄λ„, readObject() 둜 역직렬화 ν•˜λŠ” 과정을 ν†΅ν•œ 객체 생성은 λΆˆλ³€μ‹μ„ λ¬΄μ‹œν•˜κ³  μ΄μƒν•œ 값이 λ“€μ–΄κ°€κ²Œ λœλ‹€.

λ§Œμ•½ 이게 돈과 κ΄€λ ¨λœ 값이라면 ν•΄μ»€μ˜ μ‘°μž‘μ— μ˜ν•΄ μ—„μ²­λ‚˜κ²Œ 큰일 λ‚  수 μžˆλŠ” μœ„ν—˜ν•œ μƒν™©μ΄κ²Œ λœλ‹€.


μ»€μŠ€ν…€ 직렬화 λ°©μ–΄ 기법

이λ₯Ό λ°©μ–΄ν•˜λŠ” κ°€μž₯ κ°„λ‹¨ν•œ 기법은 readObject λ©”μ„œλ“œλ₯Ό 직렬화 κ΅¬ν˜„μ²΄ ν΄λž˜μŠ€μ— μž¬μ •μ˜ν•˜μ—¬ μœ νš¨μ„± 검사 λ‘œμ§μ„ μ†λ³΄λŠ” 것이닀. 그러면 μ—­μ§λ ¬ν™”μ˜ readObject() κ°€ 호좜되면, 직렬화 클래슀의 readObject λ©”μ„œλ“œκ°€ λŒ€μ‹  μ‹€ν–‰λ˜κ²Œ λœλ‹€.

이처럼 κΈ°λ³Έ 직렬화 λ™μž‘μ„ μ»€μŠ€ν…€ ν•œλ‹€λŠ” μ μ—μ„œ μ»€μŠ€ν…€ 직렬화 라고 λΆˆλ¦¬μš΄λ‹€.

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

    public Member(String name, int age) {
        this.name = name;
        checkPositive();
        this.age = age;
    }

    private void checkPositive() {
        if (this.age < 0) {
            throw new RuntimeException(new InvalidObjectException("age 값이 μ˜³μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."));
        }
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject(); // κΈ°λ³Έ 역직렬화 둜직 μ‹€ν–‰
        checkPositive();
    }

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

serialize-readObject

λ‹€μ‹œ 역직렬화λ₯Ό μˆ˜ν–‰ν•˜λ©΄ κ·ΈλŒ€λ‘œ checkPositive() λ©”μ„œλ“œμ˜ μœ νš¨μ„± 검사 λ‘œμ§μ— κ±Έλ € μ˜ˆμ™Έλ₯Ό λ°œμƒλ˜λŠ” 것을 확인 ν•  수 μžˆλ‹€.

μ •λ¦¬ν•˜μžλ©΄ μ—­μ§λ ¬ν™”ν• λ•Œ 쀑간에 μ–΄λ– ν•œ 곡격이 μžˆμ„μ§€ λͺ¨λ₯΄κΈ° λ•Œλ¬Έμ— κ°€μ Έμ˜¨ λ°”μ΄νŠΈ 슀트림이 μ§„μ§œ μ§λ ¬ν™”λœ 데이터라고 믿으면 μ•ˆλ˜λ©°, μ–΄λ–€ λ°”μ΄νŠΈ 슀트림이 λ„˜μ–΄μ˜€λ”λΌλ„ μœ νš¨μ„± 검사 λ“±μ˜ λ‘œμ§μ„ 톡해 μœ νš¨ν•œ μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€μ–΄ λ‚΄μ•Ό λœλ‹€λŠ” 점이닀. 만일 μ–΄κΈ‹λ‚˜λ‹€λ©΄ κ³Όκ°ν•˜κ²Œ InvalidObjectException 을 λ˜μ§„λ‹€.

 

ν•˜μ§€λ§Œ μ΄λŸ¬ν•œ ν˜•νƒœλŠ” λ‚˜μ€‘μ— μœ μ§€λ³΄μˆ˜ μΈ‘λ©΄μ—μ„œ 감점으둜 μž‘μš©ν•œλ‹€. μ™œλƒν•˜λ©΄ 같은 μœ νš¨μ„± 검사 λ‘œμ§μ„ μ€‘λ³΅ν•΄μ„œ μž‘μ„±ν•΄μ•Ό ν–ˆκΈ° λ•Œλ¬Έμ— ν•˜λ“œ μ½”λ”©ν™” 되기 λ•Œλ¬Έμ΄λ‹€. κ·Έλž˜μ„œ λ‚˜μ€‘μ— κ°œλ°œμžκ°€ μ‹€μˆ˜λ₯Ό ν•  수 μžˆλ‹€λŠ” 문제점이 λ‚˜νƒ€λ‚œλ‹€.

λ¬Όλ‘  μ§€κΈˆμ€ μ•„μ£Ό κ°„λ‹¨ν•œ 둜직이라 문제 생길 일은 μ—†κ² μ§€λ§Œ μ—¬λŸ¬κ°œμ˜ λ³΅μž‘ν•œ 둜직일 경우 λͺ¨λ₯΄κ²Œ λœλ‹€.

serialize-readObject

λ”°λΌμ„œ 역직렬화 λ°©μ–΄ 기법에 κ°€μž₯ μΆ”μ²œλ˜λŠ” 것은 직렬화 ν”„λ‘μ‹œ νŒ¨ν„΄μ„ μ΄μš©ν•˜λŠ” 것이닀. λ””μžμΈ νŒ¨ν„΄μ˜ ν”„λ‘μ‹œ νŒ¨ν„΄μ˜ λ³€ν˜• νŒ¨ν„΄μ΄λ©° ν•œλ²ˆ μ œλŒ€λ‘œ μ„€μ •λ§Œ ν•΄μ£Όλ©΄ 역직렬화λ₯Ό μ•ˆμ „ν•˜κ²Œ λ§Œλ“œλŠ” 데 ν•„μš”ν•œ λ…Έλ ₯을 쀄여쀀닀.


직렬화 ν”„λ‘μ‹œ νŒ¨ν„΄

직렬화 ν”„λ‘μ‹œ νŒ¨ν„΄(Serialization Proxy Pattern)은 직렬화λ₯Ό 원본이 μ•„λ‹Œ ν”„λ‘μ‹œ 객체가 λŒ€μ‹  μ§λ ¬ν™”λ˜κ³ , μ—­μ§λ ¬ν™”ν• λ•Œ ν”„λ‘μ‹œμ—μ„œ 원본 객체λ₯Ό λ°˜ν™˜ν•˜μ—¬ μΈμŠ€ν„΄μŠ€ν™” ν•˜λŠ” 기법이닀.

직렬화 ν”„λ‘μ‹œλ₯Ό κ΅¬μ„±ν•˜λŠ” 과정은 λ‹€μŒκ³Ό κ°™λ‹€.

  1. ν”„λ‘μ‹œ 클래슀 MemberProxyλ₯Ό λŒ€μƒ Member 클래슀의 정적 λ‚΄λΆ€ 클래슀(static inner class)둜 μ„ μ–Έν•œλ‹€.
  2. Member ν΄λž˜μŠ€μ— writeReplace() λ©”μ„œλ“œλ₯Ό μ •μ˜ν•˜μ—¬, Memberλ₯Ό μ§λ ¬ν™”μ‹œ ν”„λ‘μ‹œ 객체λ₯Ό λ°˜ν™˜ν•˜μ—¬ MemberProxyκ°€ 직렬화 λ˜λ„λ‘ μ œμ–΄ν•œλ‹€.
  3. 그리고 Member ν΄λž˜μŠ€μ— readObject() λ©”μ„œλ“œλ₯Ό μ •μ˜ν•˜μ—¬ 직접 직렬화λ₯Ό ν•˜μ§€ λͺ»ν•˜λ„둝 μ—λŸ¬λ₯Ό λ˜μ§€λ„λ‘ ν•œλ‹€. ν”„λ‘μ‹œκ°€ λŒ€μ‹  μ§λ ¬ν™”λ˜λ‹ˆ 원본이 직렬화 될 일이 μ—†κΈ° λ•Œλ¬Έμ΄λ‹€.
  4. λ§ˆμ§€λ§‰μœΌλ‘œ MemberProxy 클래슀 내에 readResolve() λ©”μ„œλ“œλ₯Ό μ •μ˜ν•˜μ—¬, ν”„λ‘μ‹œλ₯Ό μ—­μ§λ ¬ν™”ν• μ‹œ ν”„λ‘μ‹œ 객체가 μ•„λ‹Œ 원본 Member 객체λ₯Ό λ°˜ν™˜ν•˜λ„λ‘ μ œμ–΄ν•œλ‹€.
writeReplace() λŠ” 직렬화에 κ°„μ„­ν•˜λŠ” λ©”μ„œλ“œλΌ 보면되고, readResolve() λŠ” 역직렬화에 κ°„μ„­ν•˜λŠ” λ©”μ„œλ“œλΌ 보면 λœλ‹€.
class Member implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String name;
    private final int age;

    public Member(String name, int age) {
        this.name = name;
        checkPositive();
        this.age = age;
    }

    private void checkPositive() {
        if (this.age < 0) {
            throw new RuntimeException(new InvalidObjectException("age 값이 μ˜³μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."));
        }
    }

    // 직렬화 ν”„λ‘μ‹œ (정적 λ‚΄λΆ€ 클래슀)
    private static class MemberProxy implements Serializable {
        private static final long serialVersionUID = 2L;
        private final String name;
        private final int age;

        // μƒμ„±μžλŠ” 단 ν•˜λ‚˜μ—¬μ•Ό ν•˜κ³ , λ°”κΉ₯ 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ λ°›κ³  데이터λ₯Ό 볡사
        public MemberProxy(Member m) {
            this.name = m.name;
            this.age = m.age;
        }

        // 객체λ₯Ό 역직렬화 ν• λ•Œ ν˜ΈμΆœλ˜μ–΄, 역직렬화 κ²°κ³Όλ₯Ό readResolve λ°˜ν™˜κ°’μœΌλ‘œ μ„€μ •
        private Object readResolve() {
            return new Member(name, age); // μ—­μ§λ ¬ν™”λ˜λ©΄ μ΅œμ’…μ μœΌλ‘œ Member 객체λ₯Ό λ°˜ν™˜
        }
    }

    // 객체λ₯Ό 직렬화 ν• λ•Œ ν˜ΈμΆœλ˜μ–΄, 직렬화 λŒ€μƒμ„ writeReplaceλ₯Ό 톡해 ν”„λ‘μ‹œλ₯Ό λ°˜ν™˜ν•˜λ„λ‘ μ œμ–΄
    private Object writeReplace() {
        return new MemberProxy(this); // ν”„λ‘μ‹œκ°€ λŒ€μ‹  직렬화
    }

    // λŒ€μƒ 객체(Member)을 역직렬화 ν•˜μ§€ λͺ»ν•˜κ²Œ λ§‰λŠ”λ‹€.
    // μ• μ΄ˆμ— ν”„λ‘μ‹œ 객체둜 μ§λ ¬ν™”ν•˜κ³  μ—­μ§λ ¬ν™”ν•˜κΈ° λ•Œλ¬Έμ— λŒ€μƒ 객체가 역직렬화 될일이 μ—†κΈ° λ•Œλ¬Έμ΄λ‹€
    private void readObject(ObjectInputStream ois) throws InvalidObjectException  {
        throw new InvalidObjectException("ν”„λ‘μ‹œκ°€ ν•„μš”ν•΄μš”.");
    }

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

직렬화 ν”„λ‘μ‹œ κ³Όμ •

 

1. Member μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€κ³  직렬화λ₯Ό μˆ˜ν–‰ν•œλ‹€.

Member student1 = new Member("홍길동", 22);

// 직렬화
ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("Student.ser")));
out.writeObject(student1);
out.close();

 

2. writeObject κ°€ 호좜되면 writeReplace κ°€ ν˜ΈμΆœλ˜λŠ”λ°, μ΄λ•Œ MemberProxyλ₯Ό λ°˜ν™˜ν•˜μ—¬ ν”„λ‘μ‹œ 객체가 직렬화 되게 λœλ‹€. 그리고 ν”„λ‘μ‹œλŠ” Member의 멀버값을 κ·ΈλŒ€λ‘œ κ³„μŠΉν•˜μ—¬ 가지고 있게 λœλ‹€.

Serialization-Proxy
writeObject() -> writeReplace()

 

3. 역직렬화λ₯Ό μˆ˜ν–‰ν•œλ‹€. μ΄λ•Œ 직렬화 νŒŒμΌμ—λŠ” ν”„λ‘μ‹œ 객체가 λ°”μ΄νŠΈ 슀트림으둜 λ“€μ–΄ μžˆλ‹€.

// 역직렬화
ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream("Student.ser")));
Member member = (Member) in.readObject();
in.close();

System.out.println(member);

 

4. readObject κ°€ 호좜되면 ν”„λ‘μ‹œ 객체가 μƒμ„±λ˜κ²Œ λ˜λ©΄μ„œ, MemberProxy λ‚΄μ˜ readResolve κ°€ ν˜ΈμΆœλ˜μ–΄ Member 객체λ₯Ό λ°˜ν™˜ν•˜λ„λ‘ ν•œλ‹€. μ΄λ•Œ Member μƒμ„±μžμ—μ„œ μ—­μ§λ ¬ν™”ν•œ 데이터에 λŒ€ν•œ μœ νš¨μ„± 검증을 ν•˜κ²Œ λœλ‹€.

Serialization-Proxy
readObject() -> readResolve()

 

5. μ΅œμ’…μ μœΌλ‘œ ν”„λ‘μ‹œλ₯Ό 톡해 검증이 μ™„λ£Œλœ Member μΈμŠ€ν„΄μŠ€λ₯Ό μ–»κ²Œ λœλ‹€.

Serialization-Proxy
Serialization-Proxy

 

6. 전체 과정을 λ‚˜μ—΄ν•΄λ³΄λ©΄ μ•„λž˜μ™€ 같이 ν‘œν˜„ν•  수 μžˆλ‹€.

Serialization-Proxy


직렬화 ν”„λ‘μ‹œ νŒ¨ν„΄ μž₯단점

 

직렬화 ν”„λ‘μ‹œ μž₯점

  • κ°€μ§œ λ°”μ΄νŠΈ 슀트림 곡격과 λ‚΄λΆ€ ν•„λ“œ νƒˆμ·¨ 곡격을 ν”„λ‘μ‹œ μˆ˜μ€€μ—μ„œ 차단해쀀닀
  • ν•„λ“œλ“€μ„ final둜 선언해도 λ˜μ–΄μ„œ λŒ€μƒ 클래슀λ₯Ό μ§„μ •ν•œ λΆˆλ³€μœΌλ‘œ λ§Œλ“€μ–΄ μ€€λ‹€.
  • μ—­μ§λ ¬ν™”λ•Œ readObject μž¬μ •μ˜λ‘œ 일일히 μœ νš¨μ„± 검사λ₯Ό ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€. 
  • μ—­μ§λ ¬ν™”ν•œ μΈμŠ€ν„΄μŠ€μ™€ μ›λž˜μ˜ μ§λ ¬ν™”λœ μΈμŠ€ν„΄μŠ€μ˜ ν΄λž˜μŠ€κ°€ 달라도 정상 μž‘λ™ν•œλ‹€.
  • 직렬화 ν”„λ‘μ‹œλŠ” readObject 의 방어적 볡사보닀 κ°•λ ₯ν•˜λ‹€.

 

직렬화 ν”„λ‘μ‹œ ν•œκ³„

  • 직렬화 & 역직렬화 과정이 μƒλŒ€μ μœΌλ‘œ λŠλ¦¬λ‹€
  • ν΄λΌμ΄μ–ΈνŠΈκ°€ λ§ˆμŒλŒ€λ‘œ ν™•μž₯ν•  수 μžˆλŠ” ν΄λž˜μŠ€μ—λŠ” μ μš©ν•  수 μ—†λ‹€.
  • 클래슀의 ν•„λ“œ 객체가 μ„œλ‘œ μ°Έμ‘°ν•˜λŠ” 상황, 객체 κ·Έλž˜ν”„μ— μˆœν™˜μ΄ μžˆλŠ” ν΄λž˜μŠ€μ—λ„ μ μš©ν•  수 μ—†λ‹€. 

역직렬화 필터링

만일 Serializable을 κ΅¬ν˜„ν•œ 클래슀만 μ§€μ›ν•˜λŠ” ν”„λ ˆμž„μ›Œν¬λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•˜κ±°λ‚˜ λ ˆκ±°μ‹œ μ‹œμŠ€ν…œ λ•Œλ¬Έμ— μ–΄μ©”μˆ˜ 없이 ν΄λž˜μŠ€μ— 직렬화λ₯Ό μ„€μ •ν•΄μ•Ό ν•  경우, 그런데 μ—­μ§λ ¬ν™”ν•œ 데이터가 μ•ˆμ „ν•œμ§€ ν™•μ‹ ν•  수 없을 경우, 객체 역직렬화 필터링 (Deserialization Filters) 을 톡해 역직렬화할 μΈμŠ€ν„΄μŠ€λ₯Ό ν•„ν„°λ§ν•˜μ—¬ λ°©μ–΄ν•  수 μžˆλ‹€. 

역직렬화 필터링은 데이터 슀트림이 μ—­μ§λ ¬ν™”λ˜κΈ° 전에 ν•„ν„° 쑰건문을 μˆ˜ν–‰ν•˜μ—¬ νŠΉμ • 클래슀만 역직렬을 ν—ˆμš©ν•˜κ±°λ‚˜ μ•„μ˜ˆ μ œμ™Έν•˜μ—¬ 역직렬을 λͺ»ν•˜λ„둝 μ†Žμ•„ λ‚Ό 수 μžˆλ‹€.

역직렬화 필터링 클래슀인 java.io.ObjectInputFilter λŠ” JDK 9 λ²„μ „μ—μ„œ μ‚¬μš©ν•  수 μžˆλ‹€.

 

ObjectInputFilterλŠ” ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€μ΄λ‹€.

λžŒλ‹€μ‹μ„ 톡해 μ—­μ§λ ¬ν™”ν•˜λŠ” 객체의 클래슀λͺ…을 λΉ„κ΅ν•΄μ„œ ObjectInputFilter.Status.ALLOWED 와 ObjectInputFilter.Status.REJECTED λ₯Ό  각각 λ°˜ν™˜ν•˜λ„λ‘ μ„€μ •ν•˜λŠ” 일급 ν•¨μˆ˜λ₯Ό λ§Œλ“€κ³ , setObjectInputFilter() λ₯Ό 톡해 ν•„ν„°λ₯Ό ObjectInputFilter 객체에 λ“±λ‘ν•œλ‹€. 그리고 역직렬화λ₯Ό μˆ˜ν–‰ν•˜λ©΄ λΈ”λž™λ¦¬μŠ€νŠΈμ— κ±Έλ¦° ν΄λž˜μŠ€λŠ” μ˜ˆμ™Έκ°€ λ°œμƒλ˜κ²Œ λœλ‹€.

// 역직렬화가 ν—ˆμš©λ˜λŠ” 클래슀
class SuccessDeserializer implements Serializable {
}

// 역직렬화가 λΉ„ ν—ˆμš© λ˜λŠ” 클래슀
class NegativeDeserializer implements Serializable {
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
    SuccessDeserializer successObj = new SuccessDeserializer();
    NegativeDeserializer nagativeObj = new NegativeDeserializer();

    String filename = "filter.ser";

    // 직렬화 -------------------
    ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(filename)));
    out.writeObject(successObj);
    out.writeObject(nagativeObj);
    out.close();

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

    // 1. 역직렬화 ν•„ν„° λ§Œλ“€κΈ°
    ObjectInputFilter filter = (filterInfo) -> {
        Class<?> classObj = filterInfo.serialClass();

        // ν™”μ΄νŠΈ 리슀트
        if (classObj.getName().equals("SuccessDeserializer")) {
            return ObjectInputFilter.Status.ALLOWED; // SuccessDeserializer 클래슀일 경우 ν—ˆμš©
        }

        System.out.println("Rejected :" + classObj.getSimpleName());
        return ObjectInputFilter.Status.REJECTED; // κ·Έ μ΄μ™Έμ—λŠ” 거절
    };

    // 2. 역직렬화 ν•„ν„° 등둝
    in.setObjectInputFilter(filter);

    // 3. ν•„ν„° 적용된 μ±„λ‘œ 역직렬화
    Object obj1 = in.readObject();
    Object obj2 = in.readObject();

    System.out.println("Class Name: " + obj1.getClass().getSimpleName());
    System.out.println("Class Name: " + obj2.getClass().getSimpleName());

    in.close();
}

ObjectInputFilter

역직렬화 필터링 λΆ„κΈ° λ‘œμ§μ„ κ΅¬μ„±ν• λ•Œ, λΈ”λž™λ¦¬μŠ€νŠΈ λ°©μ‹λ³΄λ‹€λŠ” ν™”μ΄νŠΈλ¦¬μŠ€νŠΈ 방식을 μΆ”μ²œν•œλ‹€. λΈ”λž™λ¦¬μŠ€νŠΈ 방식은 이미 μ•Œλ €μ§„ μœ„ν—˜μœΌλ‘œλΆ€ν„°λ§Œ λ³΄ν˜Έν•  수 있기 λ•Œλ¬Έμ΄λ‹€. 
λ˜ν•œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μœ„ν•œ ν™”μ΄νŠΈλ¦¬μŠ€νŠΈλ₯Ό μžλ™μœΌλ‘œ μƒμ„±ν•΄μ£ΌλŠ” μŠ€μ™“(SWAT) μ΄λΌλŠ” 도ꡬλ₯Ό μ΄μš©ν•  μˆ˜λ„ μžˆλ‹€.

객체 상속 직렬화 λ°©μ–΄ 기법

Serializableλ₯Ό κ΅¬ν˜„ν•œ ν΄λž˜μŠ€λŠ” μžμ‹ μ„ μƒμ†ν•œ μžμ† 객체듀 κΉŒμ§€ 직렬화가 κ°€λŠ₯ν•˜κ²Œ λ˜λ―€λ‘œ, μ§λ ¬ν™”μ˜ 문제λ₯Ό κ³ μŠ€λž€νžˆ μ „νŒŒλ˜κΈ° λ•Œλ¬Έμ— μƒμ†μš©μœΌλ‘œ μ„€κ³„λœ ν΄λž˜μŠ€λŠ” Serializable을 κ΅¬ν˜„ν•˜λ©΄ μ•ˆλ˜λ©°, μΈν„°νŽ˜μ΄μŠ€λ„ Serializable을 ν™•μž₯ν•˜λ©΄ μ•ˆλœλ‹€.

κ·ΈλŸ¬λ‚˜ ν™˜κ²½μ μΈ μš”μΈμœΌλ‘œ μ–΄μ©”μˆ˜ 없이 클래슀의 μΈμŠ€ν„΄μŠ€ ν•„λ“œκ°€ 직렬화와 ν™•μž₯이 λͺ¨λ‘ κ°€λŠ₯ν•˜κ²Œ κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€λ©΄ λͺ‡κ°€μ§€ 주의점이 μžˆλ‹€.

 

μ²«λ²ˆμ§ΈλŠ”, μΈμŠ€ν„΄μŠ€ ν•„λ“œμ˜ κ°’ 쀑에 λΆˆλ³€μ‹μ„ 보μž₯ν•΄μ•Όν•  게 μžˆλ‹€λ©΄ λ°˜λ“œμ‹œ ν•˜μœ„ ν΄λž˜μŠ€μ—μ„œ Object μ΅œμƒμœ„ 클래슀의 finalize() λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•˜μ§€ λͺ»ν•˜κ²Œ ν•΄μ•Όν•œλ‹€. 방법은 finalizeλ₯Ό μ˜€λ²„λΌμ΄λ”© ν•˜λ©΄μ„œ final ν‚€μ›Œλ“œλ₯Ό λ©”μ„œλ“œμ— 뢙이면 λœλ‹€. μ΄λ ‡κ²Œ ν•˜μ§€ μ•ŠμœΌλ©΄ finalizer 곡격에 μ·¨μ•½ν•΄μ§ˆ 수 μžˆλ‹€.

 

λ‘λ²ˆμ§ΈλŠ”, μΈμŠ€ν„΄μŠ€ ν•„λ“œμ€‘ κΈ°λ³Έκ°’ intλŠ” 0, ObjectλŠ” null λ“± 으둜 μ„€μ •λ˜λ©΄ μœ„λ°°λ˜λŠ” λΆˆλ³€μ‹μ΄ μžˆλ‹€λ©΄ readObjectNoData() λ©”μ„œλ“œλ₯Ό λ°˜λ“œμ‹œ μΆ”κ°€ν•΄μ•Όν•œλ‹€. readObjectNoData() λŠ” Java 4 버전 λΆ€ν„° μ§€μ›ν•˜λŠ” λ©”μ„œλ“œλ‘œ κΈ°μ‘΄ 직렬화 κ°€λŠ₯ ν΄λž˜μŠ€μ— 직렬화 κ°€λŠ₯ μƒμœ„ 클래슀λ₯Ό μƒμ†ν•˜λŠ” λ“œλ¬Έ 경우λ₯Ό μœ„ν•œ λ©”μ„œλ“œμ΄λ‹€. 이에 λŒ€ν•΄μ„œ μžμ„Ένžˆ μ•Œμ•„λ³΄μž.


readObjectNoData μ‚¬μš©λ²•

readObjectNoData λ₯Ό μ‚¬μš©ν•˜λŠ” 상황은, 기쑴에 직렬화 κ°€λŠ₯ν•œ A, B 두 ν΄λž˜μŠ€κ°€ μžˆλŠ”λ°, κ°‘μžκΈ° Bκ°€ Aλ₯Ό μƒμ†ν•˜λŠ” 상황이 λ°œμƒν• λ•Œ μΌμ–΄λ‚˜λŠ” 데이터 뢈일치 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄μ„œμ΄λ‹€.

예λ₯Ό λ“€μ–΄ Serializableλ₯Ό κ΅¬ν˜„ν•˜λŠ” Person ν΄λž˜μŠ€μ™€ Student ν΄λž˜μŠ€κ°€ μžˆλ‹€κ³  ν•˜μž. 그쀑 Student μΈμŠ€ν„΄μŠ€λ§Œ μ§λ ¬ν™”ν•˜μ—¬ μ™ΈλΆ€ 파일둜 μ €μž₯ν–ˆλ‹€κ³  ν•œλ‹€.

class Person implements Serializable {
    private static final long serialVersionUID = 1L;

    String name;
    long age;

    public Person() {
    }

    public Person(String name, long age) {
        this.name = name;
        this.age = age;
    }
}

class Student implements Serializable {
    private static final long serialVersionUID = 2L;
    String school;
    String circles;

    public Student(String school, String circles) {
        this.school = school;
        this.circles = circles;
    }
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
    Student student1 = new Student("μ„Έμ’…λŒ€ν•™κ΅", "κ²Œμž„λ™μ•„λ¦¬");

    // 직렬화
    ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("Student.ser")));
    out.writeObject(student1);
    out.close();

    // 역직렬화
    ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream("Student.ser")));
    Student student2 = (Student) in.readObject();
    in.close();

    System.out.println(student2.school);
    System.out.println(student2.circles);
}

readObjectNoData
readObjectNoData

그러면 Student.ser νŒŒμΌμ— λ‹΄κ²¨μžˆλŠ” μ§λ ¬ν™”ν•œ μ •λ³΄λŠ” Student의 company ν•„λ“œμ™€ team ν•„λ“œλ§Œ 가지고 있게 λœλ‹€.

 

그런데 κ°‘μžκΈ° λͺ…μ„Έμ„œκ°€ λ°”λ€Œμ–΄μ„œ Student ν΄λž˜μŠ€κ°€ Person 클래슀λ₯Ό μƒμ†ν•œλ‹€κ³  ν•œλ‹€. 그러면 Student ν΄λž˜μŠ€κ°€ κ°€μš©ν•˜λŠ” ν•„λ“œλ“€μ€ name, age, school, circles 총 4κ°œκ°€ 되게 λœλ‹€.

Person은 Serializable을 κ΅¬ν˜„ν•˜κ³  있기 λ•Œλ¬Έμ— Studentμ—μ„œλ„ μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•  ν•„μš”κ°€ μ—†μœΌλ‹ˆ μ½”λ“œμ—μ„œ μ œκ±°ν•΄μ€€λ‹€.

class Student extends Person {
    private static final long serialVersionUID = 2L;
    String school;
    String circles;

    public Student(String school, String circles) {
        this.school = school;
        this.circles = circles;
    }
}
public static void main(String[] args) throws IOException, ClassNotFoundException {

    // 역직렬화
    ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream("Student.ser")));
    Student student2 = (Student) in.readObject();
    in.close();

    System.out.println(student2.name);
    System.out.println(student2.age);
    System.out.println(student2.school);
    System.out.println(student2.circles);
}

readObjectNoData

κ·ΈλŸ¬λ‚˜ 상속 관계λ₯Ό μ„€μ •ν•œλ’€ 기쑴에 직렬화 데이터λ₯Ό 역직렬화 ν•˜κ²Œ 되면, Personμ—μ„œ 가지고 μžˆλŠ” nameκ³Ό ageλ₯Ό μ•Œ 수 μ—†μœΌλ―€λ‘œ 각각 기본값을 ν• λ‹Ήλ°›κ²Œ λœλ‹€.

 

세상에 ν•™μƒμ˜ 이름이 null μΌμˆ˜λ„ μ—†κ³  λ‚˜μ΄κ°€ 0μΌμˆ˜λ„ μ—†λ‹€. 

즉, μ΄λ ‡κ²Œ ν•˜μœ„ ν΄λž˜μŠ€λ‘œλΆ€ν„° λΆˆλ³€ 객체λ₯Ό μœ„λ°˜ν•˜λŠ” κ²½μš°κ°€ 생길 경우, readObjectNoData() λ©”μ„œλ“œλ₯Ό μƒμœ„ 클래슀인 Person 클래슀 내에 μ •μ˜ν•¨μœΌλ‘œμ¨ ObjectInputStreamμ—μ„œ 직렬화 데이터에 μ—†λŠ” ν•„λ“œμ— λŒ€ν•΄μ„œ 값을 μ •μ˜ν•  λ•Œ ν˜ΈμΆœλ˜λ„λ‘ μ„€μ •ν•œλ‹€.

μƒμœ„ Person의 값듀이 기본값이 μ•„λ‹Œ λ‹€λ₯Έ κ°’μœΌλ‘œ μ„€μ •ν•˜λŠ” 것이기 λ•Œλ¬Έμ— readObjectNoData() λ₯Ό Person에 μ •μ˜ν•΄μ•Όν•œλ‹€.

readObjectNoData

class Person implements Serializable {
    private static final long serialVersionUID = 1L;

    String name;
    int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private void readObjectNoData() {
        this.name = "홍길동";
        this.age = 25;
    }
}

readObjectNoData

readObjectNoData() λ₯Ό μ •μ˜ν•˜κ³  λ‹€μ‹œ 역직렬화λ₯Ό 해보면 기쑴에 name κ³Ό age에 λ©”μ„œλ“œμ— μ •μ˜ν•œ 값이 κ·ΈλŒ€λ‘œ λ“€μ–΄μ˜¨ κ±Έ 확인 ν•  수 μžˆλ‹€.


싱글톀 역직렬화 λ°©μ–΄

μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜ λ‚΄μ—μ„œ μœ μΌν•˜κ²Œ μ‘΄μž¬ν•˜λŠ” 싱글톀(Singleton) 객체λ₯Ό λ‹€λ₯Έ 컴퓨터에 μ „μ†‘ν•˜λŠ” κ³Όμ •μ—μ„œ 직렬화 & 역직렬화λ₯Ό ν•˜κ²Œ 되면 μœ μΌμ„±μ΄ κΉ¨μ Έ 더이상 싱글톀 객체가 μ•„λ‹ˆκ²Œ λ˜λŠ” 문제점이 μžˆλ‹€.

μ΄λŸ¬ν•œ μ‹±κΈ€ν†€μ˜ μ—­μ§λ ¬ν™”μ˜ λŒ€μ‘ λ°©μ•ˆμœΌλ‘œ 직렬화 ν”„λ‘μ‹œμ—μ„œ 닀루어 λ³΄μ•˜λ˜ readResolve() λ©”μ„œλ“œλ₯Ό μ •μ˜ν•˜μ—¬ κΈ°μ‘΄ 싱글톀 객체λ₯Ό λ°˜ν™˜ν•˜λ„λ‘ μ œμ–΄ν•΄μ£Όλ©΄ λœλ‹€. 그리고 싱글톀 클래슀의 ν•„λ“œλ“€μ„ λͺ¨λ‘ transient μ œμ–΄μž 섀정을 ν•˜μ—¬ 직렬화에 μ œμ™Έμ‹œν‚€λ„λ‘ ν•˜λ©΄ 역직렬화에 μ™„λ²½νžˆ λ°©μ–΄κ°€ κ°€λŠ₯해진닀.

μžμ„Έν•œ 과정을 μ•„λž˜ ν¬μŠ€νŒ…μ„ μ°Έκ³ ν•˜κΈΈ λ°”λž€λ‹€.

 

[JAVA] β˜• 싱글톀 객체가 κΉ¨μ Έλ²„λ¦¬λŠ” 경우 (역직렬화 / λ¦¬ν”Œλ ‰μ…˜)

싱글톀 객체 싱글톀 κ°μ²΄λŠ” 단 ν•˜λ‚˜μ˜ μœ μΌν•œ 객체λ₯Ό 의미 ν•œλ‹€. ν•΄λ‹Ή μΈμŠ€ν„΄μŠ€κ°€ λ¦¬μ†ŒμŠ€λ₯Ό 많이 μ°¨μ§€ν•˜λŠ” 무거운 μΈμŠ€ν„΄μŠ€μΌλ•Œ, λ©”λͺ¨λ¦¬ μ ˆμ•½μ„ μœ„ν•΄ μΈμŠ€ν„΄μŠ€κ°€ ν•„μš”ν•  λ•Œ λ˜‘κ°™μ€ μΈμŠ€ν„΄μŠ€λ₯Ό

inpa.tistory.com


μ΄λ ‡κ²Œ μ—­μ§ˆλ ¬ν™”λ₯Ό λ°©μ–΄ν•˜λŠ” μ—¬λŸ¬κ°€μ§€ 기법에 λŒ€ν•΄μ„œ μ•Œμ•„λ³΄μ•˜λ‹€.

ν•˜μ§€λ§Œ μ°©κ°ν•˜μ§€ 말아야 할것은 역직렬화 ν•„ν„°λ§μ΄λ‚˜ ν”„λ‘μ‹œλ₯Ό μ‚¬μš©ν•΄λ„ μ™„λ²½ν•˜κ²Œ 역직렬화 곡격을 λ°©μ–΄ν•  수 μ—†λ‹€λŠ” 점이닀. κ·Έλž˜μ„œ κ΄€λ ¨λœ λͺ¨λ²” 사둀λ₯Ό λ”°λΌμ„œ 직렬화 κ°€λŠ₯ ν΄λž˜μŠ€λ“€μ„ 곡격에 λŒ€λΉ„ν•˜λ„λ‘ μž‘μ„±ν•œλ‹€ ν•˜λ”λΌλ„ μ—¬μ „νžˆ μ·¨μ•½ν•˜κΈ° λ•Œλ¬Έμ— κ°€μž₯ ν™•μ‹€ν•˜κ³  μ•ˆμ „ν•œ 방법은 μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ 역직렬화λ₯Ό μ•„μ˜ˆ μ•ˆν•˜λŠ” 것이닀.


# 참고자료

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

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