-
[김영한 스프링] 53. 스프링 타입 컨버터 - 포맷터(Formatter) & 포맷터를 지원하는 컨버전 서비스Spring/스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 2023. 10. 11. 02:26
포맷터 - Formatter
Converter는 입력과 출력 타입에 제한이 없는, 범용 타입 변환 기능을 제공한다.
이번에는 일반적인 웹 애플리케이션 환경을 생각해 보자. 불린 타입을 숫자로 바꾸는 것 같은 범용 기능보다는 개발자 입장에서는 문자를 다른 타입으로 변환하거나, 다른 타입을 문자로 변환하는 상황이 대부분이다.
앞서 살펴본 예제들을 떠올려 보면 문자를 다른 객체로 변환하거나 객체를 문자로 변환하는 일이 대부분이다.
웹 애플리케이션에서 객체를 문자로, 문자를 객체로 변환하는 예
- 화면에 숫자를 출력해야 하는데, Integer -> String 출력 시점에 숫자 1000 -> 문자 "1,000" 이렇게 1000 단위에 쉼표를 넣어서 출력하거나, 또는 "1,000"라는 문자를 1000이라는 숫자로 변경해야 한다.
- 날짜 객체를 문자인 "2021-01-01 10:50:11"와 같이 출력하거나 또는 그 반대의 상황
Locale
여기에 추가로 날짜 숫자의 표현 방법은 Locale 현지화 정보가 사용될 수 있다.
이렇게 객체를 특정한 포멧에 맞추어 문자로 출력하거나 또는 그 반대의 역할을 하는 것에 특화된 기능이 바로 포맷터(Formatter)이다. 포맷터는 컨버터의 특별한 버전으로 이해하면 된다.
Converter vs Formatter
- Converter는 범용(객체 객체)
- Formatter는 문자에 특화(객체 -> 문자, 문자 -> 객체) + 현지화(Locale)
- Converter의 특별한 버전
포맷터 - Formatter 만들기
포맷터(Formatter)는 객체를 문자로 변경하고, 문자를 객체로 변경하는 두 가지 기능을 모두 수행한다.
- String print(T object, Locale locale) : 객체를 문자로 변경한다.
- T parse(String text, Locale locale) : 문자를 객체로 변경한다.
Formatter 인터페이스
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> {
}MyNumberFormatter
main/java/hello/typeconverter/formatter/MyNumberFornatter 생성
package hello.typeconverter.formatter; import lombok.extern.slf4j.Slf4j; import org.springframework.format.Formatter; import java.text.NumberFormat; import java.text.ParseException; import java.util.Locale; @Slf4j public class MyNumberFormatter implements Formatter<Number> { @Override public Number parse(String text, Locale locale) throws ParseException { log.info("text={}, locale={}", text, locale); // 1,000 -> 1000 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); return instance.format(object); } }
"1,000"처럼 숫자 중간의 쉼표를 적용하려면 자바가 기본으로 제공하는 NumberFormat 객체를 사용하면 된다. 이 객체는 Locale 정보를 활용해서 나라별로 다른 숫자 포맷을 만들어준다.
parse()를 사용해서 문자를 숫자로 변환한다. 참고로 Number 타입은 Integer, Long과 같은 숫자 타입의 부모 클래스이다.
print()를 사용해서 객체를 문자로 변환한다.
MyNumberFormatterTest
package hello.typeconverter.formatter; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import java.text.ParseException; import java.util.Locale; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; class MyNumberFormatterTest { MyNumberFormatter formatter = new MyNumberFormatter(); @Test void parse() throws ParseException { Number result = formatter.parse("1,000", Locale.KOREA); assertThat(result).isEqualTo(1000L); // Long 타입 주의 } @Test void print() { String result = formatter.print(1000, Locale.KOREA); assertThat(result).isEqualTo("1,000"); } }
test/java/hello/typeconverter/formatter/MyNumberFornatterTest 생성
parse()의 결과가 Long이기 때문에 isEqualTo(1000L)을 통해 비교할 때 마지막에 L을 넣어주어야 한다.
실행
참고
스프링은 용도에 따라 다양한 방식의 포맷터를 제공한다.
Formatter 포맷터
AnnotationFormatterFactory 필드의 타입이나 애노테이션 정보를 활용할 수 있는 포맷터
자세한 내용은 공식 문서를 참고하자.
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#format포맷터를 지원하는 컨버전 서비스
컨버전 서비스에는 컨버터만 등록할 수 있고, 포맷터를 등록할 수는 없다. 그런데 생각해 보면 포맷터는 객체 -> 문자, 문자 -> 객체로 변환하는 특별한 컨버터일 뿐이다.
포맷터를 지원하는 컨버전 서비스를 사용하면 컨버전 서비스에 포맷터를 추가할 수 있다. 내부에서 어댑터 패턴을 사용해서 Formatter가 Converter처럼 동작하도록 지원한다.
FormattingConversionService는 포맷터를 지원하는 컨버전 서비스이다.
DefaultFormattingConversionService는 FormattingConversionService에 기본적인 통화, 숫자 관련 몇 가지 기본 포맷터를 추가해서 제공한다.
FormattingConversionServiceTest
test/java/hello/typeconverter/formatter/FormattingConversionServiceTest 생성
package hello.typeconverter.formatter; import hello.typeconverter.converter.IpPortStringConverter; import hello.typeconverter.converter.StringToIpPortConverter; import hello.typeconverter.type.IpPort; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.format.support.DefaultFormattingConversionService; import static org.assertj.core.api.Assertions.*; public class FormattingConversionServiceTest { @Test void formattingConversionService() { DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); // 컨버터 등록 conversionService.addConverter(new StringToIpPortConverter()); conversionService.addConverter(new IpPortStringConverter()); // 포맷터 등록 conversionService.addFormatter(new MyNumberFormatter()); // 컨버터 사용 IpPort ipPort = conversionService.convert("127.0.0.1:8080", IpPort.class); assertThat(ipPort).isEqualTo(new IpPort("127.0.0.1", 8080)); // 포맷터 사용 String convertString = conversionService.convert(1000, String.class); assertThat(convertString).isEqualTo("1,000"); Long convertLong = conversionService.convert("1,000", Long.class); assertThat(convertLong).isEqualTo(1000L); } }
DefaultFormattingConversionService 상속 관계
FormattingConversionService는 ConversionService 관련 기능을 상속받기 때문에 결과적으로 컨버터도 포맷터도 모두 등록할 수 있다. 그리고 사용할 때는 ConversionService가 제공하는 convert를 사용하면 된다.
추가로 스프링 부트는 DefaultFormattingConversionService를 상속받은 WebConversionService를 내부에서 사용한다.
실행
출처 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2
'Spring > 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술' 카테고리의 다른 글
[김영한 스프링] 55. 파일 업로드 - 소개 & 프로젝트 생성 & 세팅 (1) 2023.10.12 [김영한 스프링] 54. 스프링 타입 컨버터 - 포맷터 적용하기 & 스프링이 제공하는 기본 포맷터 (1) 2023.10.11 [김영한 스프링] 52. 스프링 타입 컨버터 - 스프링에 Converter 적용하기 & 뷰 템플릿에 컨버터 적용하기 (1) 2023.10.11 [김영한 스프링] 51. 스프링 타입 컨버터 - 타입 컨버터(Converter) & 컨버전 서비스(ConversionService) (1) 2023.10.10 [김영한 스프링] 50. 스프링 타입 컨버터 - 소개 (1) 2023.10.05