스프링 메세지 컨버터 관련 내용을 구글링 한 적 있었는데,
유명강사님 강의내용을 정리한 것 같은 똑같은 내용의 글들이였다.
강의를 보지는 않았지만 유입물의 내용이 좋았다.
하지만 분명히 더 궁금한 내용들이 있을텐데,
강의내용만 정리 되어있고,
내부적으로는 어떻게 동작하는지 설명이 별로 없어서 뜯어보았다
1. 메세지 컨버터란 무엇일까
예를들어 한국인과 일본인이
서로의 본토 언어로 대화 하려고 하면 대화가 안되겠죠
자바객체와 C++객체에도 마찬가지 일겁니다.
무언의 공통어인 영어로 중간에서 번역해준다면 대화가 통할겁니다.
번역하는 과정은 직렬화 역직렬화개념이라고 볼 수 있습니다.
이 무언의 공통어가 요즘 웹에서 JSON 입니다
예시에서 영어가 JSON이라고 보시면 됩니다
메세지 컨버터란
자바 객체를 JSON뿐만 아니라 다양한 형식(String, ByteArray 등등 많음)들로 변환가능하게끔 해주는것이다.
스프링에서는 HttpMessageConverter 라는 인터페이스를 제공한다.
코드는 아래에서 살펴 볼 것이다.
Http + MessageConverter란
http 요청의 body 부분을 다양한 형식으로 변환하여 편리하게 가져오거나 던지게 해준다.
2. HttpMessageConvertersAutoConfiguration 클래스 살펴보기
먼저 스프링부트 자동설정부분에서 호출하는 HttpMessageConvertersAutoConfiguration 클래스를 살펴봅시다.
package org.springframework.boot.autoconfigure.http;
//...생략
@AutoConfiguration(
after = {GsonAutoConfiguration.class, JacksonAutoConfiguration.class, JsonbAutoConfiguration.class}
)
@ConditionalOnClass({HttpMessageConverter.class})
@Conditional({NotReactiveWebApplicationCondition.class})
@Import({JacksonHttpMessageConvertersConfiguration.class, GsonHttpMessageConvertersConfiguration.class, JsonbHttpMessageConvertersConfiguration.class})
public class HttpMessageConvertersAutoConfiguration {
//...생략
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters((Collection)converters.orderedStream().collect(Collectors.toList()));
}
//...생략
}
HttpMessageConverters 인스턴스를 생성해주는 모습입니다. 따라가봅시다
3. HttpMessageConverters 클래스 살펴보기
HttpMessageConverter 인터페이스를 구현한 HttpMessageConverters 클래스는 아래와 같습니다.
package org.springframework.boot.autoconfigure.http;
//...생략
public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>> {
//...생략
public HttpMessageConverters(Collection<HttpMessageConverter<?>> additionalConverters) {
this(true, additionalConverters);
}
public HttpMessageConverters(boolean addDefaultConverters, Collection<HttpMessageConverter<?>> converters) {
List<HttpMessageConverter<?>> combined = this.getCombinedConverters(converters, addDefaultConverters ? this.getDefaultConverters() : Collections.emptyList());
combined = this.postProcessConverters(combined);
this.converters = Collections.unmodifiableList(combined);
}
private List<HttpMessageConverter<?>> getDefaultConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList();
if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport", (ClassLoader)null)) {
converters.addAll((new WebMvcConfigurationSupport() {
public List<HttpMessageConverter<?>> defaultMessageConverters() {
return super.getMessageConverters();
}
}).defaultMessageConverters());
} else {
converters.addAll((new RestTemplate()).getMessageConverters());
}
this.reorderXmlConvertersToEnd(converters);
return converters;
}
//...생략
}
스프링에서 딱히 다른 설정을 하지않으면
WebMvcConfigurationSupport 클래스에 getMessageConverters 메서드를 호출하는걸로 보이네요.
따라가봅시다
4. WebMvcConfigurationSupport 클래스 살펴보기
package org.springframework.web.servlet.config.annotation;
//...생략
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
//...생략
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList();
this.configureMessageConverters(this.messageConverters);
if (this.messageConverters.isEmpty()) {
this.addDefaultHttpMessageConverters(this.messageConverters);
}
this.extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new ResourceRegionHttpMessageConverter());
if (!shouldIgnoreXml) {
try {
messageConverters.add(new SourceHttpMessageConverter());
} catch (Error var3) {
}
}
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
messageConverters.add(new AtomFeedHttpMessageConverter());
messageConverters.add(new RssChannelHttpMessageConverter());
}
Jackson2ObjectMapperBuilder builder;
if (!shouldIgnoreXml) {
if (jackson2XmlPresent) {
builder = Jackson2ObjectMapperBuilder.xml();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
} else if (jaxb2Present) {
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
}
if (kotlinSerializationJsonPresent) {
messageConverters.add(new KotlinSerializationJsonHttpMessageConverter());
}
if (jackson2Present) {
builder = Jackson2ObjectMapperBuilder.json();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
} else if (gsonPresent) {
messageConverters.add(new GsonHttpMessageConverter());
} else if (jsonbPresent) {
messageConverters.add(new JsonbHttpMessageConverter());
}
if (jackson2SmilePresent) {
builder = Jackson2ObjectMapperBuilder.smile();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
}
if (jackson2CborPresent) {
builder = Jackson2ObjectMapperBuilder.cbor();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
}
}
static {
ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
kotlinSerializationJsonPresent = ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader);
}
//...생략
}
엄청 긴 로직이 들어가있는데요.
중요한건 디폴트 코드와 아래에 의존성 체크부분입니다.
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new ResourceRegionHttpMessageConverter());
if (jackson2Present) {
builder = Jackson2ObjectMapperBuilder.json();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
static {
//...생략
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
//...생략
}
json을 중점으로 보면,
jackson2Present -> true면 MappingJackson2HttpMessageConverter 를 추가해주는 모습인데요
나는 com.fasterxml.jackson.databind.ObjectMapper 이라는 의존성을 추가해준적 없는데?
네 아래에 보시면 스프링부트에 starter-web 의존성 추가해주시면 자동으로 딸려옵니다.
결론!
이러하여서 다른 블로그들에 보면
스프링부트 기본 메세지컨버터 3가지라고 설명하는겁니다
1. ByteArrayHttpMessageConverter - 바이트
2. StringHttpMessageConverter - 문자열
3. Jackson2ObjectMapperBuilder - json
이제부턴 코드들이 너무 길어서 코드는 생략하도록 할게요
JSON 쪽을 보면 MappingJackson2HttpMessageConverter 인스턴스를 생성해줍니다.
그리고 AbstractJackson2HttpMessageConverter 추상클래스를 상속받습니다. 따라갑니다.
writeInternal 메서드
반환타입 @ResponseBody 체크 - ObjectMapper의 writeValue 메서드 사용 - Java 객체를 JSON 데이터로 변환
readInternal 메서드
파라미터 @ RequestBody 체크 - ObjectMapper의 readValue 메서드 사용 - JSON 데이터를 Java객체로 변환
jackson라이브러리에서 제공하는 ObjectMapper는 스프링하실때 자주보게되실겁니다.
아래에 링크보시면서 따로 공부하시는걸 추천드립니다
https://jenkov.com/tutorials/java-json/jackson-objectmapper.html#jackson-databind
다른 라이브러리로는 ModelMapper라는 라이브러리도 자주 씁니다!
이번글은 여기까지 쓰도록 하겠습니다!
참고자료
https://honeymon.io/tech/2018/03/13/spring-boot-mvc-controller.html
'스프링&스프링부트' 카테고리의 다른 글
[spring] AOP(관점 지향 프로그래밍) 이해하기: 실용적인 예제로 배우는 AOP (1) | 2024.01.21 |
---|---|
[Spring] yml 암호화를 해보자(feat. jasypt) (2) | 2024.01.03 |
[Spring] validation @NotNull @NotEmpty @NotBlank (0) | 2023.10.24 |