Table of contents

ViewResolve

컨텐츠 네고시에이션 뷰리졸버는 뷰리졸버중의 하나로 요청의 accept 헤더에 따라 응답이 달라진다. accept 헤더는 브라우저가 어떠한 타입의 본문의 응답을 원한다라고 서버에 알려주기 위한 용도의 헤더이다.

기본적인 동작으로, 요청이 들어오면 해당 요청의 응답을 만들 수 있는 모든 view를 찾아낸다. 그 과정에서 accept 헤더와 view의 타입을 비교해서 view를 선택한다. 이처럼 클라이언트가 원하는 view를 판단하기 가장 좋은 정보는 accpet 헤더이다.

하지만, accept를 제공하지 않는 요청도 많이 있다. 이에 대비해서 format이라는 파라미터를 사용할 수 있다.

"/path?format=pdf"

컨텐츠 네고시에이션이 처음 나왔을 때는 "/users/create.json" 과 같은 요청도 지원했지만 현재는 지원하지 않는다.

실습

이번에는 컨텐츠 네고시에이션을 이용하여 요청은 JSON으로 보내고 응답은 XML을 받아보자.

@Test
    public void createUser_XML() throws Exception {
        String userJson = "{\"username\":\"jch\", \"password\":\"123\"}";

        movckMvc.perform(post("/users/create")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_XML)
                .content(userJson))
            .andExpect(status().isOk())
            .andExpect(xpath("/User/username").string("jch"))
            .andExpect(xpath("/User/password").string("123"));
    }

HttpMessageConverters에서 작성한 테스트 코드이다. 우리는 컨트롤러에서 accept 헤더에 따른 분기를 작성하지 않았지만, 스프링 MVC의 컨텐츠 네고시에이션 뷰 리졸버가 해당 분기를 처리해준다. 물론 스프링 MVC의 다른 구성요소도 관여한다.

하지만 테스트 코드를 실행해보면 406 응답이 반환되면서 테스트가 실패한다. 로그를 살펴보면 org.springframework.web.HttpMediaTypeNotAcceptableException가 발생했다는 사실을 알 수 있다. HttpMediaTypeNotAcceptableException는 미디어를 처리할 HTTP 메시지 컨버터가 존재하지 않는 경우에 발생한다.

HTTP 메시지 컨버터는 HttpMessageConvertersAutoConfiguration로 인해서 설정된다. XML을 처리하는 메시지 컨버터가 존재하지 않는 이유 알아보기 위해 HttpMessageConvertersAutoConfiguration에서 import된JacksonHttpMessageConvertersConfiguration를 살펴보자.

@Configuration
@ConditionalOnClass(ObjectMapper.class)
@ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "jackson", matchIfMissing = true)
protected static class MappingJackson2HttpMessageConverterConfiguration {

    @Bean
    @ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class, ignoredType = {
        "org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter",
        "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" })
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(
        ObjectMapper objectMapper) {
        return new MappingJackson2HttpMessageConverter(objectMapper);
    }

}

@Configuration
@ConditionalOnClass(XmlMapper.class) // 1
@ConditionalOnBean(Jackson2ObjectMapperBuilder.class)
protected static class MappingJackson2XmlHttpMessageConverterConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter(
        Jackson2ObjectMapperBuilder builder) {
        return new MappingJackson2XmlHttpMessageConverter(
            builder.createXmlMapper(true).build());
    }

}

MappingJackson2HttpMessageConverter가 JSON 메시지를 컨버팅하며, MappingJackson2XmlHttpMessageConverterConfiguration가 XML을 컨버팅한다.

// 1을 살펴보면 XmlMapper라는 클래스가 있을 때만 XML 컨버터가 등록된다는 것을 알 수 있다. 현재는 XmlMapper가 클래스패스에 없기 떄문에 등록되지 않는다.

<dependency>
   <groupId>com.fasterxml.jackson.dataformat</groupId>
   <artifactId>jackson-dataformat-xml</artifactId>
   <version>2.9.6</version>
</dependency>

jackson-dataformat-xml를 의존성에 추가해야 XmlMapper가 클래스패스에 등록된다. 이제 테스트가 통과하면서 XML이 응답되는 것을 알 수 있다. 이처럼 웹 MVC에서 제공하는 컨텐츠 네고시에이션 뷰리졸버와 메시지 컨버터를 이용하면 별도의 분기문 없이 요청 헤더에 따라 데이터를 다른 타입으로 반환할 수 있다.

해당 포스팅은 스프링 부트 개념과 활용 강의 내용을 토대로 작성하였습니다.