
타입 변환은 스스로 처리하려면 매우 번거로운 일이다.
하지만 생각해보면 Spring에서는 타입 변환을 위해 애쓴 기억이 없다.
Spring이 다 알아서 처리해주기 때문이다.
Spring은 어떻게 타입을 내 입맛에 맞춰 알아서 변환해주는걸까?
Spring의 타입 변환의 주체로는
MessageConverter와 ConversionService가 있다.
두 객체의 차이에 대해 가볍게 알아보고 ConversionService를 중점적으로 알아보자.

@ResponseBody
@PostMapping("/url")
public Object method(@RequestBody Data data) { // ** message body(JSON) 데이터를 Data 객체로 변환 **
return data; // ** Data 객체를 message body에 JSON 형식으로 변환 **
}
MessageConverter는 message body 본문을 Java 객체로 변환할 때 사용되는데,
API 통신에서 JSON 등의 형식을 주고받을 때 사용된다.
message body로부터 JSON 데이터를 전달받는 @RequestBody를 처리하거나
@ResponseBody를 통해 객체를 JSON 형식으로 변환할 때 MessageConverter가 동작한다.

@PostMapping("/url/{number}")
public String method(@PathVariable Integer number, @ModelAttribute Data data)
ConversionService는 URL Query Parameter와 HTML Form 데이터를
Java 객체로 변환할 때 사용된다.
@RequestParam, @PathVariable, @ModelAttribute 등
key=value 형식의 String 데이터를 객체로 변환할 때 ConversionService가 동작한다.

class Student {
String name;
int age;
}
name=kim&age=20 // ** ModelAttribute 사용 시 Student 객체 자동 변환
information=kim,20 // ** 객체 자동 변환 불가 **
사용자가 입력한 값은 String으로 전달된다.
key=value 형태로 적절하게 문자열이 구분되어 있다면
@ModelAttribute를 사용하여 객체로 쉽게 바인딩할 수 있지만,
하나의 value 내부에서도 문자열을 적절히 구분하여 객체로 바인딩하려면
별도의 Converter를 등록해야만 한다.
public interface Converter<S, T> {
T convert(S source);
}
입력 받은 문자열(source) 내부에서 데이터를 구분하여
Student 객체로 반환하는 Converter 인터페이스를 구현하면
하나의 value 문자열에서도 객체로 바인딩할 수 있다.

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter());
}
}
Config 클래스에서 WebMvcConfigurer의
addFormatter(FormatterRegistry registry) 메서드를 오버라이딩하여
addConverter() 메서드로 Converter를 등록해서 사용하면
Spring 내부에서 DefaultConversionService에 등록하여
@RequestParam 등에서 객체를 바인딩할 때 자동으로 적용된다.

<li th:text="${student}"</li>
<li th:text="${{student}}"</li>
타임리프에서 '{{}}'를 사용하면 ConversionService가 적용된다.
객체가 다른 객체의 필드로 선언되어 있어도 적용이 가능하다.
물론 '${}'를 사용할 때 toString()을 오버라이딩했다면 크게 문제되지 않는다.
참고로 th:field 속성을 사용한 변수는 ConversionService가 자동으로 적용된다.

1000 → 1,000 // ** 문자열을 쉼표 처리가 된 형식의 숫자로 변환 **
20250402 → 2025-04-02 // ** 문자열을 날짜 형식으로 변환 **
Formatter은 Converter의 버전 중 하나로 일반적인 문자열과 객체의 변환이 아닌
형식화된 데이터의 문자열과 객체를 변환하는 도구다.
public interface Printer<T> {
String print(T object, Locale locale);
}
public interface Parser<T> {
T parse(String text, Locale locale) throws ParseException;
}
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
Formatter을 사용하기 위해서는 Formatter<T> 인터페이스를 구현하여
print(), parse() 메서드를 오버라이딩해야 한다.
Locale 정보를 적용할 수 있다.
Formatter을 등록하는 방식은 Converter와 동일하며,
마찬가지로 convert() 메서드를 호출해서 사용할 수 있다.
다른 점은 Spring 내부에서 DefaultConversionService가 아닌 Formatter도 등록 가능한
DefaultFormattingConversionService에 등록하여 사용한다.

class Item {
@NumberFormat(pattern = "###,###")
private Integer price;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime registerDate;
}
필드에 @NumberFormat 애노테이션을 선언하여 패턴을 설정하면
해당 데이터가 뷰에 출력될 때 정해진 숫자 형식의 문자열로 출력된다.
필드에 @DateTimeFormat 애노테이션을 선언하여 패턴을 설정하면
해당 데이터가 뷰에 출력될 때 정해진 날짜 형식의 문자열로 출력된다.

MessageConverter와 ConversionService의 차이를 알아봤다.
어느 상황에서 어떤 객체가 필요한지 이해하자.
아마 ConversionService의 Converter를 직접 커스텀할일이 무조건 생길 것만 같다.
그럴 땐 당황하지 말자.