ObjectMapper는 Bean으로 등록해서 사용하자
ObjectMapper는 Bean으로 등록해서 사용하자
'ObjectMapper'는 Java 객체와 JSON 데이터 간의 변환으로 인해 Java 기반 프로젝트에서 많이 사용되는 클래스인데요.
ObjectMapper는 생성 비용 등의 이유로 사용될 때마다 new 연산자로 생성하는 것보다 bean으로 등록하여 사용하는 것이 좋다고 생각하는데, 그 생각의 근거를 스스로 정리하기 위해 글을 작성했습니다.
(생성 비용이나 메모리 등, 미세한 차이일 수 있지만 불필요한 자원의 낭비를 막을 수 있는 부분이라고 생각합니다.)
ObjectMapper란?
ObjectMapper는 JSON 데이터를 Java 객체로 역직렬화 하거나, 반대로 Java 객체를 JSON 데이터로 직렬화할 때 사용되는 Jackson 라이브러리의 클래스로 'jackson-databind' 의존성을 추가하여 사용할 수 있습니다.
스프링 부트의 경우 'spring-boot-starter-web' 의존성에 'spring-boot-starter-json' 의존성이 포함되어 있으며, 또 해당 의존성에 'jackson-databind' 의존성이 포함되어 있기 때문에 Jackson 라이브러리를 따로 추가해주지 않아도 됩니다.
또한 스프링 부트에서는 'JacksonAutoConfiguration' 클래스를 통해 ObjectMapper가 bean으로 등록되지 않은 경우(@ConditionalOnMissingBean) Jackson2ObjectMapperBuilder를 통해 ObjectMapper를 빈으로 등록하여 사용하고 있는데요.
컨트롤러에서 @RequestBody, @ResponseBody 어노테이션을 통해 클라이언트로부터 들어오고 나가는 JSON 데이터가 Java 객체와 변환되는 것도 이것 때문입니다.
(MappingJackson2HttpMessageConverter 내부에서 빈으로 등록된 ObjectMapper가 사용됩니다.)
ObjectMapper를 Bean으로 등록해서 사용해야 하는 이유
1. 스레드 안전성
먼저 빈으로 등록해서 사용해도 되는 이유인 스레드 안전성(thread safe)입니다.
Mapper instances are fully thread-safe provided that ALL configuration of the instance occurs before ANY read or write calls.
(ObjectMapper 공식 문서 내용 중 일부)
공식 문서의 내용처럼 ObjectMapper는 Thread-safe 한 구조를 가지고 있기 때문에 빈으로 등록해서 사용해도 문제가 없습니다.
2. 초기화 시간 단축
@Slf4j
@Import(ObjectMapperConfig.class)
@SpringBootTest
class DemoApplicationTests {
@Autowired
private ObjectMapper beanObjectMapper;
@Test
void testBeanObjectMapper() throws JsonProcessingException {
String jsonString = "{\"name\":\"AAA\", \"category\":\"a\", \"price\":\"10000\", \"barcode\":\"e13ba12fg95\"}";
long startTime = System.nanoTime();
// ObjectMapper를 사용하여 변환 또는 다른 작업 수행
Product product = beanObjectMapper.readValue(jsonString, Product.class);
log.info("product: " + product);
long endTime = System.nanoTime();
long duration = (endTime - startTime) / 1_000_000; // 실행 시간(밀리초) 계산
log.info("Using Spring Bean ObjectMapper: Execution time: {} ms", duration);
}
// Using Spring Bean ObjectMapper: Execution time: 1 ms
@Test
void testNewObjectMapper() throws JsonProcessingException {
String jsonString = "{\"name\":\"AAA\", \"category\":\"a\", \"price\":\"10000\", \"barcode\":\"e13ba12fg95\"}";
long startTime = System.nanoTime();
// ObjectMapper를 사용하여 변환 또는 다른 작업 수행
ObjectMapper newObjectMapper = new ObjectMapper();
Product product = newObjectMapper.readValue(jsonString, Product.class);
log.info("product: " + product);
long endTime = System.nanoTime();
long duration = (endTime - startTime) / 1_000_000; // 실행 시간(밀리초) 계산
log.info("Using New ObjectMapper: Execution time: {} ms", duration);
}
// Using New ObjectMapper: Execution time: 66 ms
@ToString
@Getter
@Setter
static class Product {
private String name;
private String category;
private BigDecimal price;
private String barcode;
}
}
(빈으로 등록하는 경우와 new 연산자로 생성하는 경우를 비교하는 간단한 테스트 코드)
테스트 코드를 통해 확인할 수 있는 것처럼 new 연산자를 통해 ObjectMapper를 생성하는 것과 bean으로 등록된 것을 사용하는 것은 작은 단위(ms)지만 소요되는 시간의 차이가 발생합니다.
때문에 bean으로 등록된 인스턴스를 재활용하게 되면 생성하는 시간을 절약할 수 있습니다.
3. 메모리 사용량 최적화
ObjectMapper 인스턴스를 bean으로 등록하여 사용하면 스프링 애플리케이션의 라이프사이클 동안 해당 bean이 한 번만 생성되며, 빈으로 등록된 ObjectMapper를 재사용함으로써 메모리 사용량을 줄일 수 있습니다.
4. 유지보수
새로운 인스턴스를 생성하는 방식의 경우 ObjectMapper의 설정이 변경되었을 때 사용되는 곳마다 해당 설정을 적용해줘야 하는데요.
빈으로 등록하여 사용하는 경우 해당 빈의 설정만 변경하면 되기 때문에 중복되는 코드가 줄어들고 유지보수성이 향상됩니다.
***
특정 클래스에서 사용하는 ObjectMapper에 대해 별도의 설정이 필요한 경우 해당 클래스의 ObjectMapper를 static으로 만들어서 사용하는 방법이 있습니다.
< 참고 자료 >
https://www.baeldung.com/spring-boot-customize-jackson-objectmapper