Formatter

Converter는 보통 타입 변환을 제공하는데, 이것에 더해 어떤 숫자에 자릿수마다 , 를 붙이고 싶거나 날짜 정보를 원하는 방식으로 변환하고 싶을 때는 컨버터를 사용하기에는 부적절하다. 이를 해결하기 위해 사용하는 것이 바로 Formatter이다. 즉, 포맷터는 컨버터의 특별한 버전이라고 생각하면 편하다.

또한, Formatter는 Locale 변수를 받아 각 국가에 따라 다른 결과물을 출력할 수 있도록 구현할 수도 있다.

  • Integer → String
    • 1000 → “1,000”
    • “1,000” → 1000
  • Date → String
    • “2023-02-22 18:55:00”

Formatter 인터페이스

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> { }

공식 Docs

기본적인 Fomatter 인터페이스이다. Fomatter를 상속받는 구현체는 다음 2가지의 메서드를 구현해야한다.

  • String print(T object, Locale locale)
    • T → String
  • T parse(String text, Locale locale)
    • String → T
  • Locale 변수를 받아 각 국가에 어울리는 값을 반환하도록 구현할 수도 있다.
    • 10000 → “10,000” or “1만원”
    • Date.now() → “2023-02-22” or “02-22-2023”

컨버터는 단방향 타입 변환만을 제공하는데 비해 포맷터는 양방향 변환을 지원한다. 이 때문에, 포맷터를 등록할 때 하나의 포맷터만 등록하면 양방향 컨버터를 등록하는 것과 동일한 것이다.

DateFormatter

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를 참조하자.

AnnoationFormatterFactory

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
      • 적용 대상의 클래스 정보가 담긴 변수이다.

이는 예제를 보면서 이해하는게 더 빠르니 예제를 보도록 하자.

NumberFormatAnnotationFormatterFactory

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>
    • NumberFormat 애노테이션을 특정 지었다.
  • Set<Class<?>> getFieldTypes() {}
    • 숫자에 관련한 Class들을 포함시키고 있다. 해당 클래스만 적용하겠다는 의미이다.
  • Formatter<Number> configureFormatterFrom() {}

결과적으로 위의 포맷터 팩토리를 사용함으로써 실제로 사용할 때에는 애노테이션을 사용하는 것만으로도 자동적으로 변환 작업이 가능하도록 구현할 수 있게 된다.

스프링 기본 Formatter

스프링은 포맷터에 관한 다양한 구현체를 제공한다.

숫자, 날짜, 통화, 인터넷 주소의 구현체를 제공한다. 그 중 몇가지를 추려서 정리해보자.

  • AbstractNumberFormatter
    • 숫자 포맷터를 위한 추상 클래스
    • print, parse는 구현되어있으나, 자식 클래스에게 getNumberFormat을 구현하도록 되어있다.
    • CurrencyStyleFormatter
      • 통화 스타일의 숫자 값으로 변환한다.
      • 123456789 → “₩123,456,789.00”, “$123,456,789.00”, “¥123,456,789.00”
    • PercentStyleFormatter
      • 해당 숫자의 퍼센트 값으로 변환한다.
      • 123456789 → “12,345,678,900%”
    • NumberStyleFormatter
      • 숫자를 자릿수마다 짤라서 ,를 포함시킨다. ( 기본값: 3자리 )
      • 123456789 → “123,456,789”
      • 생성자에 패턴을 넣어 원하는 방식으로 숫자를 표현할 수 있는 기능을 지원한다.
        • 패턴에 대한 자세한 내용은 이 글을 참고하자.
  • DateFormatter
    • 날짜 포맷터
    • 생성자에 패턴을 넣어 원하는 방식으로 날짜를 표현할 수 있다.
      • 패턴에 대한 자세한 내용은 이 글을 참고하자.

Formatter Annotation

AnnotationFormatterFactory로 구현된 기본 Annotation은 다음 2가지가 있다.

  • @NumberFormat
    • 숫자 관련 포맷터를 지원하는 애노테이션이다.
    • Style Enum을 통해 원하는 포맷을 지정할 수 있다.
    • pattern 파라미터에 원하는 포맷을 지정할 수 있다.
  • @DateTimeFormat
    • 날짜 관련 포맷털르 지원하는 애노테이션이다.
    • ISO Enum을 통해 원하는 포맷을 지정할 수 있다.
    • pattern 파라미터에 원하는 포맷을 지정할 수 있다.
profile
백엔드 개발자 지망생

0개의 댓글