티스토리 뷰
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 의 확장을 고민하고 있었다면 도움이 되길 바란다.
'Java > spring' 카테고리의 다른 글
spring boot 3 migration#02 WebSecurityConfigurerAdapter (0) | 2023.05.14 |
---|---|
spring boot 3 migration#01 ListenableFuture (1) | 2023.05.14 |
domain 에서 프레임워크 의존을 제거할 수 있을까 (4) | 2022.11.20 |
TransactionTemplate 을 이용한 트랜잭션 제어 (0) | 2022.03.06 |
spring boot aop 에서 JDK dynamic proxy 이용하는 법 (1) | 2021.05.04 |
- Total
- Today
- Yesterday
- spring cloud
- Jackson
- generics
- java8
- javascript
- backend개발환경
- frontcode
- Spring
- go-core
- frontend개발환경
- java
- clean code
- servlet
- JPA
- Design Pattern
- db
- http
- MySQL
- TEST
- OOP
- code
- EffectiveJava
- Kotlin
- 정규표현식
- toby
- mariadb
- Git
- JavaScript Core
- programming
- DesignPattern
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |