이번 포스트에서는 자바의 직렬화와 역직렬화에 대해 간단히 정리해보겠다. 직렬화는 객체를 전송 가능한 데이터 형태로 변환하는 과정이고, 역직렬화는 그 데이터를 다시 객체로 변환하는 과정이다.
직렬화: 객체 데이터를 연속적인 데이터(스트림)로 변환하여 전송 가능한 형태로 만드는 것.
역직렬화: 직렬화된 데이터를 받아서 다시 객체 형태로 만드는 것.
예를 들어, Person person = new Person("문정현");라는 객체를 { "name" : "문정현"}로 변환하는 것이 직렬화다. 반대로, { "name" : "문정현"} 데이터를 받아 Person 객체를 생성하는 것이 역직렬화다.
직렬화를 수행하려면 클래스가 Serializable 인터페이스를 구현해야 한다.
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private String name;
private int age;
private String email;
private transient String exclude; // 직렬화에서 제외할 수 있다
@Override
public String toString() {
return String.format("User name: %s, age: %s, email: %s", name, age, email);
}
}
위와 같이 생성된 클래스는 java.io.ByteArrayOutputStream과 java.io.ObjectOutputStream을 이용해 직렬화할 수 있다.
@Slf4j
public class SerializeTest {
@Test
@DisplayName("Serialize Test")
public void SerializeTest() {
User user = User.builder()
.name("Happykoo")
.age(30)
.email("rudals4549@gmail.com")
.build();
String serializedUserBase64;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(user);
// 직렬화(byte array)
byte[] serializedUser = baos.toByteArray();
// byte array를 base64로 변환
serializedUserBase64 = Base64.getEncoder().encodeToString(serializedUser);
log.debug("serializedUserBase64 >> {}", serializedUserBase64);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
serializedUserBase64 >> rO0ABXNyABduZXQuaGFwcHlrb28ubW9kZWwuVXNlcqLhsk0TYPUEAgADSQADYWdlTAAFZW1haWx0ABJMamF2YS9sYW5nL1N0cmluZztMAARuYW1lcQB+AAF4cAAAAB50ABRydWRhbHM0NTQ5QGdtYWlsLmNvbXQACEhhcHB5a29
객체를 자바 직렬화한 후 데이터(클래스) 구조가 변경되면, 클래스 버전이 맞지 않아 역직렬화가 제대로 되지 않고 에러가 발생한다.
serialVersionUID는 클래스의 고유 버전 ID로, 클래스 구조가 변경될 때마다 값이 달라진다.
serialVersionUID를 직접 설정하지 않으면, 내부적으로 클래스 구조 정보를 이용해 자동으로 추가되지만 클래스 구조 변경 시 문제가 발생할 수 있다.
따라서, 자주 변경되는 클래스에는 자바 직렬화를 지양하는 것이 좋다. 변경에 취약하여 예상치 못한 예외가 발생할 가능성이 높기 때문이다.
참고 자료
자바 직렬화 확실히 알고 가기
Java Serialization 상세 설명