들어가기 전에
이 글은
- Spring Boot 3.5.5
- Java 17
기준으로 작성되었다.
확인한 바로는 Spring Boot 4 이후 버전에서는 이런 문제가 생기지 않고 JSON으로 통신되었다.
개요
Spring Boot 프로젝트에서 XML 사용 시 주의할 점이 있다.
정확히는 com.fasterxml.jackson.dataformat:jackson-dataformat-xml 라이브러리 사용할 때이다.
라이브러리 추가
단순히 jackson-dataformat-xml 라이브러리를 프로젝트에 추가하는 것 만으로 영향을 끼치는 부분이 있다.
RestClient, RestTemplate, WebClient 같은 통신 모듈의 기본 요청 Body 형식이 XML로 변경된다.
원래는 아무 설정 안 한 상태면 JSON이 기본값이다.
원인
Spring Boot는 ClassPath에 jackson-dataformat-xml이 있으면 MappingJackson2XmlHttpMessageConverter를 자동으로 빈으로 등록한다.
그리고 HttpMessageConverters에 추가한다.
그러면 RestClient가 객체를 직렬화할 때 컨버터 목록을 확인하는데 XML 컨버터가 등록되어 있기 때문에 사용되게 된다.
해결 방법
가장 확실한 해결 방법으로는 RestClient를 사용할 때 Content-Type 헤더를 명시적으로 사용하는 것이다.
String res = restClient
.post()
.uri("http://localhost:3030")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body(person)
.retrieve()
.body(String.class);또는 RestClient를 만들 때 defaultHeader를 설정할 수 있다. (Bean으로 등록하는 것도 방법)
RestClient restClient = RestClient
.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();위에서 말한 방법대로 RestClient를 사용할 때 헤더를 직접 설정해주는 것이 사이드이펙트 위험이 적을 것이다.
그 외에 방법도 작성하겠지만 그리 추천하지는 않는다. 더 복잡하기도 하고 글로벌 설정을 건드리는 것이기 때문에 어떤 영향도가 생길지 파악하기 어렵다.
RestClient를 Bean으로 등록하며 messageConverters 설정을 직접 해주는 방법이다. 사용하는 곳에서 주입받아서 사용하면 된다.
@Bean
public RestClient restClient(RestClient.Builder builder) {
return builder
.messageConverters(converters -> {
// 1. 기존 컨버터 중 XML 컨버터 제거 또는 순서 뒤로 밀기
converters.removeIf(c -> c instanceof MappingJackson2XmlHttpMessageConverter);
// 2. 필요하다면 마지막에 다시 추가 (우선순위를 가장 낮게 설정)
converters.add(new MappingJackson2XmlHttpMessageConverter());
})
.build();
}HttpMessageConverters Bean을 직접 생성해주는 방법이다. RestClient Bean도 생성해주고 주입 받아서 써야한다.
@Bean
public RestClient restClient(RestClient.Builder builder) {
return builder.build();
}
@Bean
public HttpMessageConverters customConverters() {
// JSON 컨버터 생성
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
// XML 컨버터 생성
MappingJackson2XmlHttpMessageConverter xmlConverter = new MappingJackson2XmlHttpMessageConverter();
// 리스트에 순서대로 담기 (JSON이 XML보다 앞에 오도록)
return new HttpMessageConverters(true, List.of(jsonConverter, xmlConverter));
}XmlMapper Bean 등록
xmlMapper를 사용하기 위해서 직접 Bean으로 등록하는 것 만으로 Spring Boot Application의 모든 API 응답의 Body 형식이 XML로 바뀌게 된다.
- xmlMapper를 커스텀 및 전역으로 등록해서 사용하고 싶은 경우 Bean으로 등록하는 경우가 있다.
- Response Header는
application/json인데 내용은 XML 형태인 특이한 현상이 발생한다.
@Configuration
public class XmlConfig {
@Bean
public XmlMapper xmlMapper() {
return XmlMapper.builder()
.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true)
.build();
}
}원인
Spring MVC의 Content Negotiation(내용 협상) 때문이다.
클라이언트가 요청할 때 Accept 헤더가 없거나 */*로 보낼 경우 Spring은 등록된 컨버터 중 처리 가능한 것을 선택한다.
XmlMapper를 직접 Bean으로 등록하게 되면 Spring Boot가 기본으로 제공하는 설정보다 사용자 정의 빈을 우선시하면서 JSON보다 XML 설정이 더 우선적으로 적용되게 된다.
해결 방법
전체적으로 모든 응답을 XML로 하는 것을 원하는게 아니라면 XmlMapper는 Bean으로 등록하지 않고 따로 Util로 빼거나 직접 만들어서 사용하는게 안전하고 좋다.
또는 ObjectMapper도 같이 Bean으로 등록하는 방법도 있다.
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}