직렬화,역직렬화 in Spring

슬링민키·2025년 5월 16일
31

Spring

목록 보기
2/3
post-thumbnail

서론 🚀

우아한테크코스에서는 각자 하나의 주제를 가지고 발표를 하는 테코톡을 한다. 발표를 준비하기 전에 어떤 주제를 선정할지 고민을 하였다.

주제를 선정하는 당시의 나는 @ResponseBody, @RequestBody에 대해서 학습하고 있었을 때인데, 이때 직렬화와 역직렬화라는 개념을 처음 알게 되었고 이 부분이 신기하여 주제를 선정하게 되었다.

직렬화 부분을 처음에는 간단하다고 생각하고 준비했는데, 직렬화의 역사는 너무 길었다. 처음에는 자바의 직렬화 부분을 먼저 준비하였는데, 자바의 직렬화만 이야기해도 10분을 넘기게 되어 빼게 되었다.

직렬화 , 역직렬화 in Spring 🌱

직렬화란?

먼저 간단하게 직렬화와 역직렬화가 무엇인지 설명해보자면, 직렬화란 Java 객체를 JSON, XML, 혹은 바이트 배열과 같이 저장하거나 전송할 수 있는 상태로 변환하는 과정이다. 이 과정을 통해 객체를 파일로 저장하거나 네트워크를 통해 전달할 수 있게 된다.

역직렬화란?

역직렬화는 이와 반대의 과정으로, 직렬화된 데이터를 다시 Java 객체로 복원하는 것이다. 예를 들어 JSON 형식의 요청 본문을 컨트롤러에서 User 객체로 변환하는 것이 역직렬화이다.

그렇다면 우리는 왜 직렬화와 역직렬화를 사용하는 것일까?

우리가 실제로 사용하는 user 객체는 JVM의 힙 메모리에 존재한다. 이 객체는 메모리 주소를 기준으로 관리되기 때문에 외부 시스템이 직접적으로 이해할 수 없다. 따라서 외부 시스템도 이해할 수 있도록 user 객체를 문자열 혹은 바이트 형태로 바꿔주는 작업이 필요하다.

Spring에서는 이 과정을 직접 처리하지 않고도 @RequestBody@ResponseBody 어노테이션을 통해 비교적 간단하게 사용할 수 있다.

예를 들어, @RequestBodyHTTP 요청의 본문을 Java 객체로 변환해야 할 때 사용하고, @ResponseBody는 그 반대로 Java 객체를 HTTP 응답 본문으로 직렬화할 때 사용한다.

HttpMessageConverter


이때 직렬화와 역직렬화를 실제로 수행하는 핵심 컴포넌트가 HttpMessageConverter이다. 이 인터페이스는 요청/응답 데이터를 객체로 변환하거나 객체를 응답 데이터로 변환할 수 있는 기능을 제공한다.

주요 메서드는 다음과 같다:

  • canRead() / canWrite(): 특정 클래스와 MediaType(예: application/json)에 대해 읽기/쓰기 가능한지 boolean 값으로 반환한다.
  • read() / write(): 실제로 객체를 읽거나 쓰는 동작을 수행한다.


Spring에서는 다양한 구현체를 등록해 두고, 요청이 올 때마다 우선순위에 따라 canRead() / canWrite() 여부를 체크한 뒤, 가능한 구현체를 선택하여 사용한다.

만약 직렬화와 역직렬화를 실행하게 된다면 우선순위대로 messageConverter를 내려오게 되면서 canRead()를 확인하며 true가 된다면 해당 구현체를 사용하게 된다.

현재 Spring에서 기본적으로 JSON을 처리하는 구현체는 MappingJackson2HttpMessageConverter이다. 만약 다른 구현체를 사용하고 싶다면 우선순위를 조정하여 앞에 등록하면 된다.

MappingJackons2HttpMessageConveter

이번에 더 알아볼MappingJackson2HttpMessageConverter는 Spring에서 Json을 직렬화 역직렬화 할 때 기본으로 사용하는 Jackson에서 제공한 converter이다.

MappingJackson2HttpMessageConver의 상속 구조를 따라가다 보면 httpMessageConvter를 구현하고 있다는 점을 확인할 수 있다.


MappingJackson2HttpMessageConverter는 내부적으로 ObjectMapper를 사용하여 JSONJava 객체 간 변환을 수행한다.

해당 객체는 Spring에 등록되어 있어서 우리가 직접 @AutoWired를 통해서 확인 할 수 있다. 따라서 등록되어 있는 ObjectMapper 를 설정할 수 도 있다.

보이는 것 과 같이 생성자에서 ObjectMapper를 직접 주입받고 있는 것을 확인 할 수 있다.


실제로 canRean() 구현 안에서도 ObjectMapper를 사용하고 있는 점을 확인할 수 있다.

Jackson 직렬화,역직렬화 조건

여기까지 간단하게 MappingJackson2HttpMessageConverter를 알아보았으나 이를 사용하기 위한 몇가지 조건이 있다.

역직렬화 조건

먼저 역직렬화를 하기 위한 몇가지 조건을 살펴보자.

  • 클래스의 필드가 public이라면 문제없이 값을 주입할 수 있다.

  • 하지만 일반적으로는 필드를 private으로 선언하기 때문에, 이 경우 Jackson은 기본 생성자로 객체를 만든 뒤, setter 메서드를 통해 값을 주입한다.

  • 이때 저 기본생성자의 publicprivate으로 변경해보았는데 잘 작동하는 점이 신기하였다. 작동하는 이유는 시간이 남는다면 추가할 예정이다.아마 리플렉션을 사용하는 것 같다.

  • 만약 setter가 없어도, Reflection을 통해 private 필드에 직접 값을 주입할 수 있다.

직렬화 조건

다음으로 직렬화의 조건을 살펴보자.

  • 직렬화할 때도 필드가 public이라면 바로 JSON으로 변환된다.

  • private으로 설정되어 있다면 getter의 역할이 중요해진다.
  • getter를 사용하여 프로퍼티를 찾아 직렬화 하게 된다.

  • get 뒤의 단어 첫 글자를 소문자로 바꾼 후 에 이를 프로퍼티 이름으로 사용하여 직렬화 하게 된다.

  • 여기서 사용하는 방식은 사실 Java Bean 규칙을 따르는 것이다. 궁금하다면 더 찾아보길 바란다.

  • 더 자세히 본다면 필드가 없다 하더라도 return 값을 직렬화 하고 있는 점을 확인 할 수 있다.

    🤔

  • 만약 개발자가 별 생각 없이 직렬화 하는 객체에 get이라는 이름으로 시작하는 메서드를 사용한다면 그대로 해당 return 값을 직렬화를 하게 된다. 즉 의도치 않은 직렬화가 생길 수 있다는 점이다.

  • 반대로 말하면 필드에 있는 값만 직렬화 할 수 있는 것이 아닌 개발자가 원하는 만큼 직렬화 할 수 있다.

여기까지 몇가지 조건을 살펴보았다.


궁금증 🤔

근데 한가지 의문이 들었다. 역직렬화 할 때 setter가 없다 하더라도 리플렉션을 사용하여 Jackson은 값을 가져와 역직렬화 해준다.

하지만 직렬화의 경우에는 getter가 없으면 필드의 값을 리플렉션을 사용하지 않고 오류가 발생한다.

왜 이 부분에 예외를 두었는지를 고민해보았다.고민과 검색 끝에 몇가지 내가 내린 결론을 써보자면

  1. 요즘 개발에서는 불변성을 중요시 여겨 setter가 없는 객체가 많다. 즉 setter가 없을 수 있다는 점을 고려하여 리플렉션을 허용한다.

  2. 자바의 철학인 자바 Bean 규칙에 따라 프로퍼티 이름은 get뒤의 이름을 사용한다는 점을 지킨다. 필드의 접근에 관해서는 getter/setter를 통해서만 접근한다.

  3. 역직렬화를 하는 과정보다는 직렬화를 하는 과정이 더 엄격해야한다고 생각이 든다. 역직렬화 과정에서는 어떻게든 데이터 매핑을 찾아 시도할 수 있겠지만, 직렬화를 보내는 데이터는 무결성이 보장되어야한다고 생각든다.

하지만 지나가던 크루인 머랭이 3번 생각에 대해 질문을 주었다. 클라이언트에게 직렬화 해주었을 때 만약 직렬화가 잘못되었다면 클라이언트 측에서 정보를 확인 할 수 없게 된다. 하지만 역직렬화의 문제의 경우는 서버측에 장애가 생길 수 있다는 점을 이야기 했다.

이 이야기에서도 나는 굳이 더 안전하게 다뤄야하는 것을 정해보자면 직렬화가 더 중요하다고 생각든다. 직렬화가 잘못되었다면 역직렬화 역시 문제가 생길 수 밖에 없기 때문이다. 네트워크 전송의 시작점이 직렬화이기 때문이다. 오히려 서버의 장애가 생긴다는 것은 우리가 해결할 수 있는 문제이지 않을까 싶다.

  • Json 직렬화,역직렬화 에서의 경우이다. byte[]의 직렬화 역직렬화에서는 역직렬화의 보안은 매우 중요하게 다뤄지고 있다.

위의 조건들이 아니어도 되는데?

처음에 이 조건들을 직접 확인 해보기 위해서 따라 해보았는데 찾아본 내용들과 많이 다른 경우가 있었다. 기본 생성자가 없는 경우에도 잘 작동이 되자 예제를 만들 수 없었다.

알고보니 나의 Intellij에서 컴파일의 설정이 -parameters 가 켜져 있어서 런타임에도 클래스 정보를 Jackson에서 알 수 있었다. 보통 개발 환경에서 build를 Gradle로 하게 된다면 자동으로 켜지게 되는데 꺼져있는 경우를 고려하고 공부하는 것이 다양한 상황을 맞이할 때 더 좋을 것 같다는 생각이 들었다.

Jackson 어노테이션 활용

이제는 이 직렬화를 하는 과정에서 더 잘 사용할 수 있는 몇가지 어노테이션을 소개해보고자 한다.

@JsonIgnore

- 직렬화/역직렬화 대상에서 제외시켜,보안이나 불필요한 데이터 전송을 막을 수 있다.

이 사진은 앞에서 소개한 의도치 않은 직렬화가 생기는 상황이다.

이때 메서드 위에 @JsonIgnore라는 어노테이션을 붙여주게 된다면 해당 메서드는 직렬화 되는 과정에서 제외된다.

메서드 뿐만 아니라 필드에서도 사용할 수 있는데 해당 필드를 return 하더라도 직렬화 되지 않는 것을 볼 수 있다.

@JsonCreator & @JsonProperty

다음으로 알아볼 어노테이션은 @JsonCreator@JsonProperty 이다.

앞선 조건에서 역직렬화 과정에서 기본생성자가 있어야 한다고 말했다. 하지만 해당 어노테이션을 사용한다면 기본 생성자 대신 해당 메서드를 통해 역직렬화 할 수 있다.

따라서 기본 생성자가 없어도 된다. 이때 인자가 2개 이상이 된다면 @JsonProperty를 명시해주어야한다. Json의 키 이름과 생성자의 파라미터를 매핑 해줌으로써 역직렬화를 구체적으로 알려주는 것이다.

@JsonProperty 역시 필드에도 사용이 가능한데 이 경우에는 getter가 생략되어도 직렬화가 가능해진다.

또한 ()안에 프로퍼티 이름을 설정 해 줄 수도 있다.

마무리하며 🥭

이번 포스팅에서는 직렬화와 역직렬화에 대해 간단히 알아보았다. 그 중에서도 JSON을 사용하는 방식, 특히 Spring에서 기본으로 사용하는 Jackson 기반의 MappingJackson2HttpMessageConverter를 중심으로 설명하였다.

물론 JSON을 다루는 라이브러리는 Jackson 외에도 Gson, FastJson, Protobuf 등 다양한 방식이 존재하고, application/xml, text/csv 같은 다른 미디어 타입도 처리해야 하는 상황이 생길 수 있다.

따라서 특정 라이브러리 하나를 깊게 파는 것보다는, Spring에서의 전체적인 직렬화 흐름과 어노테이션 중심의 유연한 사용법을 익히는 것이 실제 서비스 개발에서는 더 유용하지 않을까 생각해보게 되었다.

profile
하루하루는 성실하게 인생 전체는 되는대로

2개의 댓글

comment-user-thumbnail
2025년 5월 16일

직렬화에 대해서 잘 모르고 넘겼었는데, 포스팅 해주셔서 감사합니다!

1개의 답글