[2/24] TIL - HttpMessageConverter, Converter, Formatter
오늘 배운 내용은!
HttpMessageConverter의 내부 동작 원리
HttpMessageConverter가 사용이 될 때는 http 요청과 응답이 오갈 때이다.
- 사용처
- HTTP 요청 : @RequestBody, HttpEntity<>, RequestEntity<>
- HTTP 응답 : @ResponseBody, HttpEntity<>, ResponseEntity<>

스프링에서 컨트롤러의 내부 동작 흐름을 살펴보면 HttpMessageConverter는 아마 알맞은 handler를 호출하기 전과 호출된 후로 사용 될 것이다.
대표적인 HttpMessageConverter로
ByteArrayHttpMessageConverter -> byte[] 데이터를 처리
StringHttpMessageConverter -> String 데이터를 처리
MappingJackson2HttpMessageConverter - > 제이슨 데이터를 처리
[요철 데이터를 읽는 경우]
1. 컨트롤러에서 @ResquestBody 혹은 HttpEntity<> 로 파라미터로 바인딩 한다.
@RestController
public class ExampleController {
// consumes
@PostMapping(value = "/example", consumes = "application/json")
public String example(@RequestBody RequestDto dto) {
return dto;
}
}
2. MessageConverter는 canRead() 메서드로 읽기가능 여부를 조회한다. -> 지원하는 대상 클래스인지 확인, content-type이 MediaType 지원 여부인지 확인한다.
3. read()메서드로 객체를 생성한다.
[응답 데이터를 쓰는 경우]
1. 컨트롤러에서 @ResquestBody 혹은 HttpEntity<> 로 응답이 반환된다.
2. MessageConverter는 canWrite() 메서드로 사용가능 여부를 조회한다. -> 반환 클래스가 지원 대상 클래스인지 확인, 요청헤더의 Accept를 보고 MediaType 지원여부가 가능한지 확인한다.
RequestMappingHandlerAdapter란?
http 요청에 따라 알맞은 컨트롤러를 매핑해주는 어댑터이다. 정확히 말하자면 @RequestMapping 을 처리하는 HandlerAdapter의 구현체이다. (@PostMapping , @GetMapping, @PutMapping, @PatchMapping, @DeleteMapping 등은 모두 @RequestMapping의 일종이다.)
ArgumentResolver란?

RequestMappingHandlerAdapter는 ArgumentResolver를 호출하여 Controller가 필요한 다양한 파라미터의 값을 생성한다. 마찬가지로 반환할 때는 ReturnValue Handler를 이용한다. 이때 HttpMessageConverter를 이용한다. ArgumentResolver를 통하여 값이 준비되면 해당값을 가지고 실제 Controller를 호출한다.
TypeConverter란?
Spring에서 객체의 타입을 서로 변환하는 데 사용되는 인터페이스로 Spring의 데이터 바인딩 과정에서 문자열을 특정 객체로 변환하거나 하나의 객체 타입을 다른 타입으로 변환할 때 사용한다.
사용하는 방법은 Converter 클래스를 상속받아 메서드를 오버라이딩 하면 된다. 하지만 사용할 때마다 인스턴스화 해서 사용해야하는 불편함이 있다. Converter를 편리하게 등록하고 사용할 수 있도록 만들어주는 기능이 필요하다. Spring은 String, Integer, Enum등 자주 사용되는 타입에 대한 컨버터를 제공하고 사용할 수 있도록 등록되어 있다.
ConversionService란?
Spring은 Converter를 모아서 편리하게 관리하고 사용할 수 있게 해주는 기능을 제공한다. 이것이 Conversion Service 이다.
- 컨버터를 사용할 때는 종류를 몰라도된다.
- 컨버터는 ConversionService 내부에서 숨겨진채 제공된다.
- 반환 타입, 파라미터 타입, 제네릭 등으로 ConversionService가 컨버터를 찾는다.
- 즉, 클라이언트는 ConversionService 인터페이스만 의존하면 된다.
- 컨버터 등록과 사용의 분리
Converter 적용
- Spring은 내부적으로 ConversionService를 제공한다.
- WebMvcConfigurer가 제공하는 addFomatters() 를 사용하여 Converter를 등록하면 된다.
- 클라이언트 측에서 컨버터를 사용하면 된다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFomatters(FormatterRegistry registry) {
// registry.addConverter(new "${등록할 Converter}");
registry.addConverter(new StringToPersonConverter());
registry.addConverter(new PersonToStringConverter());
}
}
Formatter란?
주로 사용자 지정 포맷을 적용해 데이터 변환을 처리할 때 사용된다. Formatter는 ConversionService와 비슷한 목적을 가지지만 문자열을 객체로 변환하거나 객체를 문자열로 변환하는 과정에서 포맷팅을 세밀하게 제어할 수 있다.
객체를 특정한 포맷에 맞춰서 문자로 출력하는 기능에 특화된것이 Fomatter이다.
Converter보다 조금 더 세부적인 기능이라고 생각하면 된다.
Locale
- 지역 및 언어 정보를 나타내는 객체.
- 언어코드 en, ko
- 국가코드 US, KR
- 특정 지역 및 언어에 대한 정보를 제공하여 국제화 및 지역화 기능을 지원한다.
- 국제화
- Locale 정보에 따라서 한글을 보여줄지 영문을 보여줄지 선택할 수 있다.
@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).format(object);
}
}
FormattingConversionService란?
ConversionService와 Formatter를 결합한 구현체로 타입 변환과 포맷팅이 필요한 모든 작업을 한 곳에서 수행할 수 있도록 설계되어 있어서 다양한 타입의 변환과 포맷팅을 쉽게 적용할 수 있다.
public class FormattingConversionServiceTest {
@Test
void formattingConversionService() {
// given
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
// Converter 등록
conversionService.addConverter(new StringToPersonConverter());
conversionService.addConverter(new PersonToStringConverter());
// Formatter 등록
conversionService.addFormatter(new PriceFormatter());
// when
String result = conversionService.convert(10000, String.class);
// then
Assertions.assertThat(result).isEqualTo("10,000");
}
}
Spring이 제공하는 Formatter
Spring은 어노테이션 기반으로 원하는 형식의 Formatter를 사용할 수 있도록 기능을 제공한다.
- Annotation
- DTO 필드들에 적용 가능
- @NumberFormat
- 숫자 관련 지정 Formatter 사용
- NumberFormatAnnotationFormatterFactory
- @DateTimeFormat
- 날짜 관련 지정 Formatter 사용
- Jsr310DateTimeFormatAnnotationFormatterFactory
@Data
public class FormatForm {
@NumberFormat(pattern = "#,###.##")
private BigDecimal price;
@DateTimeFormat(pattern = "dd-MM-yyyy")
private LocalDate orderDate;
}
JSON데이터는
@JsonFormat 나 커스텀 Deserializer를 사용하는 방식이 필요하다.
@Data
public class JsonFormatDtoV2 {
@JsonDeserialize(using = CurrencyDeserializer.class)
private BigDecimal price;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
private LocalDate orderDate;
}
- @JsonFormat은 날짜 형식이나 숫자 포맷을 지정할 수 있다.
- 콤마를 포함한 숫자는 Jackson이 자동으로 변환하지 않으므로 커스텀 처리가 필요하다.
- 커스텀 데이터의 변환이 필요한 경우 Deserialize 사용