Spring MVC - Formatter 구현 및 사용 ( Formatter와 FormattingConversionService )
스프링에 Formatter 적용하기
Formatter
Formatter는 문자에 특화된 타입 변환 Converter의 특별한 버전이다. 객체와 문자, 문자와 객체의 타입 변환시 특정 포멧으로 Locale 정보에 따라 문자를 출력하거나 또는 그 반대의 역할을 하는 특화된 기능이 포함된 인터페이스이다.
Converter는 타입 변환이 범용적으로 쓰이는 타입 변환기라고 볼 수 있으며 Formatter는 문자에 특화된 타입 변환기라고 볼 수 있다.
ex) 날짜 객체 -> 2022-03-02 16:14:00
참고
@RequestParam, @ModelAttribute, @PathVariable 등에서 사용 가능
메시지 컨버터(JSON)에는 컨버전 서비스가 적용되지 않는다.
Formatter 인터페이스
- Formatter 인터페이스는 Printer 인터페이스와 Parser 인터페이스를 상속 받는다.
- Printer<T>
- 객체를 문자로 변경하는 역할 - Parser<T>
- 문자를 객체로 변경하는 역할
@FunctionalInterface
public interface Printer<T> {
String print(T object, Locale locale);
}
@FunctionalInterface
public interface Parser<T> {
T parse(String text, Locale locale) throws ParseException;
}
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
Formatter 구현
간단하게 1000 단위로 쉼표가 들어가는 포맷이 적용되는 포맷터를 구현해보자.
@Slf4j
public class MyNumberOfFormatter implements Formatter<Number> {
@Override
public Number parse(String text, Locale locale) throws ParseException {
log.info("text={}, locale={}", text, locale);
NumberFormat format = NumberFormat.getInstance(locale);
return format.parse(text);
}
@Override
public String print(Number object, Locale locale) {
log.info("object={}, locale={}", object, locale);
NumberFormat instance = NumberFormat.getInstance(locale);
String format = instance.format(object);
return format;
}
}
위에서 구현한 포맷터를 테스트를 해보면 타입 변환과 포맷이 잘 이루어진 것을 볼 수 있다.
@Test
void parse() throws ParseException {
MyNumberOfFormatter formatter = new MyNumberOfFormatter();
Number result = formatter.parse("1,000", Locale.KOREA);
Assertions.assertThat(result).isEqualTo(1000L);
}
@Test
void print() {
MyNumberOfFormatter formatter = new MyNumberOfFormatter();
String result = formatter.print(1000, Locale.KOREA);
Assertions.assertThat(result).isEqualTo("1,000");
}
FormattingConversionService
포맷터 역시 여러 포맷터가 있을 경우 관리 및 사용이 불편하기 때문에 스프링에서 포맷터를 모아서 편리하게 사용할 수 있는 기능을 제공하는데 그것이 바로 FormattingConversionService 이다.
포맷터도 타입 변환 기능에 포맷 기능이 추가된 특별한 컨버터 이므로 FormattingConversionService 내부를 보면 ConversionService을 구현하고 있다는 것을 알 수 있다.
DefaultFormattingConversionService 사용
- 컨버터 등록 및 사용과 포맷터 등록 및 사용 모두 구현한 클래스
- addConverter()
- 컨버터와 포맷터 등록 - convert()
- 컨버터와 포맷터 사용
위에서 예시로 만든 포맷터를 등록하고 사용하는 테스트 코드를 구현해보자.
public class ConversionServiceTest {
@Test
void conversionService(){
// 컨버전 등록
DefaultConversionService conversion = new DefaultConversionService();
conversion.addConverter(new StringToIntegerConverter());
conversion.addConverter(new StringToIpPortConverter());
conversion.addConverter(new StringToIpPortConverter());
conversion.addConverter(new IpPortToStringConverter());
// 컨버전 사용
Assertions.assertThat(conversion.convert("10", Integer.class)).isEqualTo(10);
Assertions.assertThat(conversion.convert(10, String.class)).isEqualTo("10");
IpPort ipPort = conversion.convert("127.1.2.3:8080", IpPort.class);
Assertions.assertThat(ipPort).isEqualTo(new IpPort("127.1.2.3", 8080));
String ipPortString = conversion.convert(new IpPort("127.0.0.1", 8080), String.class);
Assertions.assertThat(ipPortString).isEqualTo("127.0.0.1:8080");
}
}
DefaultFormattingConversionService 를 이용하면 컨버터뿐만아니라 포맷터까지 등록 및 사용이 가능하다는 것을 알 수 있다. 아래는 DefaultFormattingConversionService 의 상속 구조이다.
스프링에 Formatter 등록하고 사용하기
Formatter 등록
- Formatter를 등록하기 위해서는 WebMvcConfigurer가 제공하는 addFormatters()메서드를
사용하여 Formatter를 등록할 수 있다. - addFormatters(FormatterRegistry registry)
- FormatterRegistry : 인터페이스 ConveterRegistry의 구현 클래스 - 참고로 위에서 만든 컨버터(String→Integer)를 등록하면 기본 컨버터가 아닌
등록한 컨버터가 호출된다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new IpPortToStringConverter());
registry.addFormatter(new MyNumberOfFormatter());
}
}
등록한 Formatter 사용
위에서 만든 포맷터인 MyNumberOfFormatter를 사용해보자.
@GetMapping("/converter")
public String converterV1(@RequestParam Integer data){
log.info("data = {}", data);
return "ok";
}
/converter?data=10,000 요청시 전달된 파라미터 문자열이 포맷이 적용된 타입 변환이 잘 이루어진 것을 볼 수있다.
스프링이 제공하는 기본 Formatter
포맷터는 기본형식이 지정되어 있기 때문에, 객체의 각 필드마다 다른 형식으로 포맷을 지정하기는 어렵다. 스프링에서는 이런 문제를 해결하기 위해 애노테이션 기반으로 원하는 형식을 지정해서 사용할 수 있는 매우 유용한 포맷터 두 가지를 기본으로 제공한다
- @NumberFormat : 숫자 관련 형식 지정 포맷터 사용
- @DateTimeFormat : 날짜 관련 형식 지정 포맷터 사용
@Data
static class Form {
@NumberFormat(pattern = "###,###")
private Integer number;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;
}
해당 클래스 필드에 넘어온 값이 바인딩 될 때 각 필드에 지정된 포맷애노테이션에 따라 포맷터가 적용이 된다.