Converter는 보통 타입 변환을 제공하는데, 이것에 더해 어떤 숫자에 자릿수마다 ,
를 붙이고 싶거나 날짜 정보를 원하는 방식으로 변환하고 싶을 때는 컨버터를 사용하기에는 부적절하다. 이를 해결하기 위해 사용하는 것이 바로 Formatter이다. 즉, 포맷터는 컨버터의 특별한 버전이라고 생각하면 편하다.
또한, Formatter는 Locale 변수를 받아 각 국가에 따라 다른 결과물을 출력할 수 있도록 구현할 수도 있다.
package org.springframework.format;
@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> { }
기본적인 Fomatter 인터페이스이다. Fomatter를 상속받는 구현체는 다음 2가지의 메서드를 구현해야한다.
String print(T object, Locale locale)
T parse(String text, Locale locale)
컨버터는 단방향 타입 변환만을 제공하는데 비해 포맷터는 양방향 변환을 지원한다. 이 때문에, 포맷터를 등록할 때 하나의 포맷터만 등록하면 양방향 컨버터를 등록하는 것과 동일한 것이다.
package org.springframework.format.datetime;
public final class DateFormatter implements Formatter<Date> {
private String pattern;
public DateFormatter(String pattern) {
this.pattern = pattern;
}
public String print(Date date, Locale locale) {
if (date == null) {
return "";
}
return getDateFormat(locale).format(date);
}
public Date parse(String formatted, Locale locale) throws ParseException {
if (formatted.length() == 0) {
return null;
}
return getDateFormat(locale).parse(formatted);
}
protected DateFormat getDateFormat(Locale locale) {
DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
dateFormat.setLenient(false);
return dateFormat;
}
}
DateFormatter dateFormatter = new DateFormatter("yyyy-MM-dd HH:mm:ss");
String print = dateFormatter.print(new Date(), Locale.KOREA);
Date parse = dateFormatter.parse("2022-02-22 11:20:30", Locale.KOREA);
해당 포맷터는 스프링에서 제공하는 지역에 따른 날짜 포맷터이다. 변환 작업을 실행할 때 Locale 값을 받는 것을 확인할 수 있는데, 이로 인해 국가에 따라 다른 시간을 제공할 수 있도록 구현할 수 있었다.
포맷터의 구현부를 보면 SimpleDateFormat을 사용하는 것을 볼 수 있는데, 이는 java가 기본으로 제공하는 변환 클래스이다. 해당 부분에 대해 자세히 알고 싶으면 Java Docs를 참조하자.
package org.springframework.format;
public interface AnnotationFormatterFactory<A extends Annotation> {
Set<Class<?>> getFieldTypes();
Printer<?> getPrinter(A annotation, Class<?> fieldType);
Parser<?> getParser(A annotation, Class<?> fieldType);
}
Formatter를 애노테이션의 형태로 사용할 수 있도록 제공되는 인터페이스이다.
Set<Class<?>> getFieldTypes()
Printer<?> getPrinter(A annotation, Class<?> fieldType)
Parser<?> getParser(A annotation, Class<?> fieldType)
<A extends Annotation> annotation
Class<?> fieldType
이는 예제를 보면서 이해하는게 더 빠르니 예제를 보도록 하자.
package org.springframework.format.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public @interface NumberFormat {
Style style() default Style.DEFAULT;
String pattern() default "";
enum Style {
DEFAULT,
NUMBER,
PERCENT,
CURRENCY
}
}
public final class NumberFormatAnnotationFormatterFactory
implements AnnotationFormatterFactory<NumberFormat> {
public Set<Class<?>> getFieldTypes() {
return new HashSet<Class<?>>(asList(new Class<?>[] {
Short.class, Integer.class, Long.class, Float.class,
Double.class, BigDecimal.class, BigInteger.class }));
}
public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation, fieldType);
}
public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation, fieldType);
}
private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
if (!annotation.pattern().isEmpty()) {
return new NumberStyleFormatter(annotation.pattern());
} else {
Style style = annotation.style();
if (style == Style.PERCENT) {
return new PercentStyleFormatter();
} else if (style == Style.CURRENCY) {
return new CurrencyStyleFormatter();
} else {
return new NumberStyleFormatter();
}
}
}
}
public class MyModel {
@NumberFormat(style = Style.CURRENCY)
private BigDecimal decimal;
}
해당 포맷터 팩토리는 @NumberFormat 애노테이션을 지원하는 팩토리이다. 차근차근히 뜯어보자.
implements AnnotationFormatterFactory<NumberFormat>
Set<Class<?>> getFieldTypes() {}
Formatter<Number> configureFormatterFrom() {}
결과적으로 위의 포맷터 팩토리를 사용함으로써 실제로 사용할 때에는 애노테이션을 사용하는 것만으로도 자동적으로 변환 작업이 가능하도록 구현할 수 있게 된다.
스프링은 포맷터에 관한 다양한 구현체를 제공한다.
숫자, 날짜, 통화, 인터넷 주소의 구현체를 제공한다. 그 중 몇가지를 추려서 정리해보자.
,
를 포함시킨다. ( 기본값: 3자리 )AnnotationFormatterFactory로 구현된 기본 Annotation은 다음 2가지가 있다.