티스토리 뷰

spring-boot-starter-web 을 이용해 웹서버 애플리케이션을 만들고 있다면 json 에 대한 핸들링은 jackson 을 이용하게 된다. 물론 jackson 외 다른 라이브러리를 이용하고 싶다면 변경할 수 있다. jackson 에서 실질적으로 json serialize/deserialize 를 담당하는 객체는 ObjectMapper 인데 스프링 부트로 애플리케이션을 만들어봤다면 별다른 설정을 하지 않아도 잘 돌아가는걸 확인할 수 있을 것이다.

 

# custom serializer/deserializer 가 필요한 경우

널리 사용되는 타입들에 대해서는 대부분 정상적으로 serialize/deserialize 가 되기 때문에 고민할 거리가 없다. 하지만 개발자가 임의로 정의한 타입에 대해 원하는 json 포맷이 있는 경우, 별도의 구현체가 필요해진다.

class BankAccount {
  private Money balance;
  
  // 생성자, getter...
}

class Money {
  private long value;
    
  // 생성자, getter...
}

BankAccount 의 인스턴스에 대해 serialize 를 하게되면 {"balance": {"value": 1000}} 형태의 json 이 나올 것이다. 반대로 deserialize 를 할때도 동일하게 Money 클래스의 구조를 모두 알고있는 상태로 json 을 작성해야한다. Money 는 VO 로써 굳이 그 내부를 세세하게 알리지말고 그냥 숫자로만 대응하게 할 수는 없을까? {"balance": 1000} 형태로 말이다. 이에 대한 구현 방식은 예전 포스팅으로 대체한다( https://multifrontgarden.tistory.com/172 ).

 

# serializer/deserializer 등록

위 참고 포스팅을 보면 전역적인 설정을 하기 위해서는 직접 구현한 구현체들을 ObjectMapper 에 등록해줘야 한다. 이게 일반적인 spring 애플리케이션을 개발하던 시절에는 어차피 ObjectMapper 빈을 직접 생성해야 했기 때문에 그 사이에 다른 설정을 추가하는게 별다른 문제가 될 여지가 없었다. 하지만 spring boot 에서는 ObjectMapper 빈을 자동구성을 통해 빈 생성을 하고 있기 때문에 중간에 우리가 만든 구현체들을 껴넣을 여지가 없다.

 

# 직접 ObjectMapper 빈 생성

@Bean
public ObjectMapper objectMapper() {
  var objectMapper = new ObjectMapper();
  
  // 다양한 설정 진행
  
  return objectMapper;
}

위처럼 직접 빈 생성을 해주면 그 사이에 설정들을 포함할 수 있다. 이 방식을 사용할 경우 애플리케이션 초기 개발단계라면 큰 문제가 없을 수 있지만 이미 운영 중인 애플리케이션이라면 문제가 발생할 여지가 많다. spring boot 가 자동으로 생성하는 ObjectMapper 빈은 모든 설정이 초기화된 기본 인스턴스가 아니라 spring boot 에서 이미 다양한 설정들을 해놓은 인스턴스이기 때문이다. 개발자가 직접 ObjectMapper 빈을 생성하는 순간 spring boot 는 더 이상 자동 구성되는 ObjectMapper 빈을 생성하지 않는데 그러면 애플리케이션 전역 ObjectMapper 빈이 직접 생성한 인스턴스로 변경되고, 자동 구성되는 설정들을 알게 모르게 이용하고 있었다면 이 설정들이 모두 바뀌게 되는 문제가 발생하기 때문이다.

 

# 자동구성으로 생성되는 ObjectMapper

spring boot 가 자동으로 생성해주는 ObjectMapper 가 궁금하다면 JacksonAutoConfiguration 클래스를 확인해보면 된다. ObjectMapper 쪽만 간단하게 확인해보자.

@Bean                                                                  
@Primary                                                               
@ConditionalOnMissingBean // 이 애노테이션으로 인해 개발자가 수동 생성하는 빈이 있으면 추가 빈을 만들지 않는다.                                              
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
	return builder.createXmlMapper(false).build();                     
}

직접 클래스를 확인해본다면 생각보다 매우 다양한 설정들을 하고 있는걸 알게 된다.

 

# 자동구성되는 ObjectMapper 에 custom serializer/deserializer 등록

spring boot 는 자동구성되는 빈에 추가적인 설정을 더 할 수 있도록 확장 포인트들을 열어주고 있다. ObjectMapper 를 핸들링하기 보다는 이미 운영중인 애플리케이션이라면 이 부분을 활용하는게 사이드 이펙트를 줄일 수 있다.

@FunctionalInterface
public interface Jackson2ObjectMapperBuilderCustomizer {
    void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder);
}

그 확장 포인트는 바로 위 인터페이스이다. 위 인터페이스는 jackson 이 아니라 spring boot 에서 제공하는 인터페이스이며, 위 인터페이스의 구현체를 빈으로 생성해주면 자동으로 그 설정을 ObjectMapper 에 넣어준다.

@Configuration
class JacksonConfiguration {
	@Bean
	public Jackson2ObjectMapperBuilderCustomizer customizer() {
		return builder -> {
			builder.serializers(/* custom serializer 들 */);
			builder.deserializers(/* custom deserializer 들 */);
		};
	}
}

FunctionalInterface 이기 때문에 위처럼 람다로 구현해서 넣어 줄 수 있다. serializer/deserializer 위주로 설명하긴 했지만 그 외에도 ObjectMapper 에 넣어줘야하는 설정이 있다면 대부분 그 설정을 해줄 수 있기 때문에 위 인터페이스를 구현해주면 자동 설정으로 들어가는 설정은 모두 취하면서 개발자가 원하는 설정들만 쏙쏙 넣어줄 수 있게 된다.

 

# 정리

spring boot 는 편리하고 쉽게 애플리케이션을 만들 수 있게 도와주는 만큼 그 내부를 개발자 입맛대로 바꾸기 위해서는 더 많은걸 알아야 한다. 위와 같은 확장 포인트를 제공해주고, 자동 설정을 통해 필수라고 할 수 있는 다양한 설정을 해주는건 좋지만 다른 설정을 넣기 위해 실제 jackson 의 확장보다는 spring boot 에서 제공하는 확장 포인트를 알아야 한다는게 조금 아쉽다. 자동 구성되는 ObjectMapper 의 확장을 고민하고 있었다면 도움이 되길 바란다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함