HttpMessageConverter가 적용되는 경우

HttpMessageConverter는 JSON, TEXT, XML 등의 데이터를 전달하는 요청과 응답 모두 사용된다.

HTTP 요청 : @RequestBody, HttpEntity<>, RequestEntity<>

HTTP 응답 : @ReponseBody, HttpEntity<>, ResponseEntity<>

 


Converter 적용 우선순위

Spring은 다양한 HttpMessageConverter를 제공하고, 요청된 메서드의 파라미터 타입과 MediaType을 체크해서 컨버터를 결정한다.

우선순위 1. ByteArrayHttpMessageConverter

- 대상 : byte[]

- MediaType(content-type) : */*

- 반환 : application/octet-stream

public byte[] byteArray(@RequestBody byte[] data){...}

 

우선순위 2. StringHttpMessageConverter

- 대상 : String

- MediaType : */*

- 반환 : text/plain

public String string(@RequestBody String data) {...}

 

우선순위 3. MappingJackson2HttpMessageConverter

- 대상 : Object, HashMap

- MediaType : application/json

- 반환 : application/json

public Data json(@RequestBody Data data) {...}

 

이 외에도 컨버터가 존재하지만, 대부분 이 세가지로 데이터 변환을 처리한다.

 


내부 구조와 동작 원리

RequestMappingHandlerAdapter

- @RequestMapping을 처리하는 HandlerAdapter의 구현체

- ArgumentResolver 호출

- HTTP 요청을 컨트롤러 메서드에 매핑하고, 이 메서드를 호출하여 결과를 반환하는 역할 수행

 

ArgumentResolver

- HandlerMethodArgumentResolver (ArgumentResolver의 실제 인터페이스)

- 컨트롤러에게 필요한 다양한 파라미터 값을 생성한다.

- 따라서 HttpServletRequest, Model, HttpEntity, @RequestBody, @RequestParam 등에 자동 바인딩이 가능해진다.

- HttpMessageConverter를 호출해서 데이터를 적절한 타입으로 변환한다.

- 사용 가능한 파라미터 목록

 

ReturnValueHandler

- HandlerMethodReturnValueHandler (ReturnValueHandler의 실제 인터페이스)

- 컨트롤러가 반환하는 값을 HTTP 응답 본문에 적절히 담아 전송한다.

- HttpMessageConverter를 호출해서 데이터를 적절한 타입으로 변환한다.

 


Converter 구현

특정 엔티티 포맷의 객체로 값을 전달받고자, 일반적인 자료형이 아닌 특별한 타입으로 변환해주는 컨버터가 필요할 때는 직접 컨버터를 생성해야 한다.

@ModelAttribute를 사용하면 컨버터가 없어서 객체에 바로 저장할 수 있었는데 왜 컨버터를 생성해야 할까?
ModelAttribute는 ?name=채진&age=25 처럼 어떤 필드가 전달되는지 명확하기 때문에 바로 객체에 저장이 가능하다.

컨버터를 생성해서 사용해야 하는 경우는 아래처럼 특수한 형태로 데이터가 저장되어 바로 필드에 저장하기 어려운 상황이다.
?person=wonuk:1200
?code=dsafkjf@code (<- @앞까지의 데이터만 DB에 저장할 가치가 있는 경우)

 

 

1. 특정 상황에 맞는 컨버터를 생성

public class StringToPersonConverter implements Converter<String, Person> {
    // source = "wonuk:1200"
    @Override
    public Person convert(String source) {
        // ':' 를 구분자로 나누어 배열로 만든다.
        String[] parts = source.split(":");

        // 첫번째 배열은 이름이다. -> wonuk
        String name = parts[0];
        // 두번째 배열은 개월수이다. -> 1200
        int months = Integer.parseInt(parts[1]);

        // 개월수 나누기 12로 나이를 구하는 로직 (12개월 단위만 고려)
        int age = months / 12;

        return new Person(name, age);
    }
}

 

 

2. 컨버터 등록(WebMvcConfigurer의 메서드 오버라이딩)

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry){
        registry.addConverter(new StringToPersonConverter());
        registry.addConverter(new PersonToStringConverter());
    }
}

 

기존에는 생성한 컨버터를 직접 ConversionService 객체를 생성한 뒤에 집어넣어줘야 했지만, 스프링에서 제공하는 ConversionService인 WebMvcConfigurer의 addFormatter 메서드를 사용하면 간편하다.

 

그리고 원래는 컨버터 등록(생성), 컨버터 사용 모두 ConversionRegistry와 ConversionService를 직접 사용해야 해서 개발자가 내부 로직으로 직접 데이터를 변환하는 것과 큰 차이가 없다.

하지만 스프링의 경우는 파라미터로 값을 전달받을 때 알아서 등록된 컨버터를 사용하고 데이터를 바인딩해주기 때문에 컨버터를 사용할 때의 코드 작성은 필요 없다.

 


Formatter 구현

포맷터는 주로 사용자 지정 포맷을 적용해 데이터를 변환할 때 사용한다.

객체를 특정한 포맷에 맞춰서 문자로 출력하는 기능에 특화된 것이 formatter이다. (converter보다 조금 더 세부적인 기능)

 

1. 포맷터 생성

@Slf4j
public class PriceFormatter implements Formatter<Number> {

    @Override
    public Number parse(String text, Locale locale) throws ParseException {
        log.info("text = {}, locale = {}", text, locale);

        // 변환 로직
        // NumberFormat이 제공하는 기능
        NumberFormat format = NumberFormat.getInstance(locale);

        // "10,000" -> 10000L
        return format.parse(text);
    }

    @Override
    public String print(Number object, Locale locale) {
        log.info("object = {}, locale = {}", object, locale);

        // 10000L -> "10,000"
        return NumberFormat.getInstance

 

 

Locale

  • 지역 및 언어 정보를 나타내는 객체.
    • 언어코드 en, ko
    • 국가코드 US, KR

2. 포맷터 등록

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry){
        registry.addConverter(new StringToPersonConverter());
        registry.addConverter(new PersonToStringConverter());
        
        // Formatter 등록
        conversionService.addFormatter(new PriceFormatter());
    }
}

 


결론

스프링 덕에 간소화된 버전으로 컨버터와 포맷터를 등록하고 사용할 수 있지만, 되도록 기존에 스프링이 제공하는 컨버터와 포맷터를 활용하는 게 베스트

 

파라미터

https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods/arguments.html

 

Method Arguments :: Spring Framework

JDK 8’s java.util.Optional is supported as a method argument in combination with annotations that have a required attribute (for example, @RequestParam, @RequestHeader, and others) and is equivalent to required=false.

docs.spring.io

 

포맷터

https://docs.spring.io/spring-framework/reference/core/validation/format.html#format-CustomFormatAnnotations

 

Spring Field Formatting :: Spring Framework

As discussed in the previous section, core.convert is a general-purpose type conversion system. It provides a unified ConversionService API as well as a strongly typed Converter SPI for implementing conversion logic from one type to another. A Spring conta

docs.spring.io