직렬화란?
Java 직렬화는 객체 또는 데이터를 바이트(byte), 이진 형태로 변환하고, 이를 파일 또는 네트워크를 통해 전송하거나 저장하는 기술입니다. 역직렬화는 바이트로 변환된 데이터를 다시 객체로 변환하는 기술입니다.
이를 통해 다음과 같이 다양한 용도로 사용될수 있습니다.
- 객체, 데이터의 저장 및 전송: 객체를 파일 시스템에 저장하거나, 네트워크에 전송하기 위해 직렬화를 통해 객체를 바이트 스트림으로 변환하여 저장하거나 전송할 수 있습니다. 이를 통해 객체 및 데이터를 `영속화`할 수 있습니다.
- 캐시: 객체를 직렬화하여 메모리에 `캐싱`하고 필요할때 역직렬화하여 사용할 수 있습니다.
- 클러스터링 및 분산시스템: 다른 노드 간에 데이터를 공유하기 위해 직렬화를 사용할 수 있습니다. 직렬화를 통해 데이터를 바이트 타입으로 보냄으로 노드 간 호환성을 유지할 수 있습니다.
왜 직렬화할까?
직렬화를 통해 데이터를 네트워크 상에서 전송함으로 인해서, 객체 또는 데이터를 이진 형태로 표현하고 효율적으로 데이터를 전송할 수 있습니다. 또한 직렬화하여 이진 형태로 보냄으로 플랫폼 간 호환성을 유지할 수 있고, 이진 형태에 암호화 같은 보안 기술을 적용하여 데이터의 무결성과 보안을 강화할 수 있습니다.
직렬화의 원리와 사용
Java 직렬화는 `Serializable 인터페이스`를 구현한 객체를 바이트 형태로 변환하는 과정입니다. 이를 위해 객체의 필드 값들을 바이트 스트림으로 변환하고, 이를 파일이나 네트워크로 전송할 수 있습니다. 이 과정에서 객체의 상태, 필드 정보, 클래스 정보 등이 저장됩니다.
직렬화할 클래스 정의
// Serializable 인터페이스를 구현하여 직렬화 가능한 클래스 생성
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return String.format("Person", name, age);
}
}
위 코드에서 `Person` 클래스는 `Serializable` 인터페이스를 구현하여 직렬화가 가능한 클래스로 만들었습니다. 아래 코드는 `Person` 클래스를 직렬화하는 코드입니다.
💡serialVeresionUID
serialVersionUID는 Java 직렬화에서 사용되는 정적 필드입니다. 이 필드는 직렬화된 클래스의 버전을 식별하는 데 사용됩니다. serialVersionUID는 java.io.Serializable 인터페이스를 구현한 클래스에서 사용됩니다.
serialVersionUID는 직렬화된 객체를 역직렬화할 때 사용됩니다. 직렬화된 객체에는 해당 클래스의 serialVersionUID 값이 포함되어 있습니다. 역직렬화 시 수신측에서는 역직렬화되는 클래스의 serialVersionUID와 직렬화된 객체의 serialVersionUID를 비교하여 호환성을 검사합니다. 두 값이 일치해야 역직렬화가 성공적으로 수행됩니다.
이를 통해 serialVersionUID를 명시적으로 정의함으로써 클래스의 버전을 관리할 수 있습니다. 즉, 클래스가 변경되었을 때 serialVersionUID를 업데이트하여 이전 버전의 객체와 호환성을 유지할 수 있습니다.
위 코드처럼 serialVersionUID를 명시적으로 정의하지 않으면 컴파일러가 컴파일시 클래스의 구조나 컴파일 시간 등을 기반으로 serialVersionUID를 자동 생성합니다. 이렇게 되면 클래스 구조 변경 시 자동 생성된 serialVersionUID 값은 변경 사항을 제대로 반영하지 못할 수 있기 때문에 명시적으로 serialVersionUID를 정의함으로써 직렬화된 객체의 버전을 더욱 명확하게 관리하는 것이 좋습니다.
serialVersionUID 정의하는 방법
private static final long serialVersionUID = 123456789L;
serialVersionUID는 long 타입의 상수 필드로 선언되어야 합니다. 값은 개발자가 임의로 선택할 수 있으며, 일반적으로 정수 또는 long 형식을 사용합니다.
직렬화
`java.io.ObjectOutputStream` 객체를 통해 `writeObject()` 메서드를 호출하여 객체를 직렬화하고 파일에 저장하였습니다.
// 객체 생성
Person person = new Person("John", 25);
// 객체를 파일에 직렬화
try {
FileOutputStream fileOut = new FileOutputStream("person.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(person);
out.close();
fileOut.close();
System.out.println("객체가 직렬화되어 파일에 저장되었습니다.");
} catch (IOException e) {
e.printStackTrace();
}
역직렬화
`java.io.ObjectOutputStream` 객체를 통해 `readObject()` 메서드를 호출하여 여객체를 역직렬화합니다.
// 파일에서 객체 역직렬화
try {
FileInputStream fileIn = new FileInputStream("person.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
Person deserializedPerson = (Person) in.readObject();
in.close();
fileIn.close();
System.out.println("파일에서 객체가 역직렬화되었습니다.");
// 역직렬화된 객체의 정보 출력
System.out.println("이름: " + deserializedPerson.getName());
System.out.println("나이: " + deserializedPerson.getAge());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Java 직렬화 단점과 대안
보안 취약성
Java 직렬화는 악의적인 사용자가 객체를 조작할 수 있는 보안 취약성을 가지고 있습니다. 이를 해결하기 위해서는 다음과 같은 대안을 고려할 수 있습니다:
- 대안 직렬화 형식 사용:
JSON, XML, 프로토콜 버퍼 등의 대안 직렬화 형식을 사용하여 보다 안전한 직렬화를 수행할 수 있습니다. 이러한 형식은 제어 가능한 직렬화 방식을 제공하여 보안 취약성을 줄일 수 있습니다. - 역직렬화 필터링:
역직렬화되는 객체에 대해 필터링을 적용하여 특정 클래스나 패키지의 역직렬화를 방지하는 기능을 사용할 수 있습니다.
호환성 문제
클래스의 변경이 발생하면 직렬화된 객체를 역직렬화하는 과정에서 호환성 문제가 발생할 수 있습니다. 이를 해결하기 위해서는 다음과 같은 대안을 고려할 수 있습니다:
- 직렬화 버전 관리:
serialVersionUID를 명시적으로 정의하여 클래스의 직렬화 버전을 관리할 수 있습니다. 이를 통해 클래스의 변경에도 serialVersionUID를 일정하게 유지하여 호환성을 보장할 수 있습니다.
성능 문제
Java 직렬화는 직렬화 및 역직렬화 과정에서 성능 문제를 가지고 있을 수 있습니다. 이를 해결하기 위해서는 다음과 같은 대안을 고려할 수 있습니다:
- 바이너리 직렬화 형식 사용:
텍스트 기반 직렬화 형식보다는 바이너리 형식인 프로토콜 버퍼나 MessagePack과 같은 직렬화 형식을 사용하여 더 효율적인 직렬화를 수행할 수 있습니다. - 직렬화 라이브러리의 선택:
성능이 우수한 직렬화 라이브러리를 선택하여 성능 문제를 완화할 수 있습니다. 예를 들어, Kryo, FST, Jackson 등의 라이브러리는 빠른 직렬화 및 역직렬화 기능을 제공합니다.
'Java' 카테고리의 다른 글
Java 리플렉션(Reflection API): 동적 코드 조작과 메타프로그래밍을 위한 Refelction API (0) | 2023.07.05 |
---|---|
Java 어노테이션(@annotation)을 활용한 커스텀 메타데이터 만들기 (0) | 2023.07.05 |
Java java.io 기반 입출력: 입출력 스트림, 콘솔 입출력, 파일 입출력, 보조 스트림 (0) | 2023.06.26 |
Java 소켓을 사용하여 단체 채팅방 만들기 (0) | 2023.06.26 |
Java try-wtih-resources로 자원 관리하기 (0) | 2023.06.23 |