ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring MVC - Formatter 구현 및 사용 ( Formatter와 FormattingConversionService )
    Spring/Spring MVC 2022. 2. 17. 11:48
    728x90
    반응형

     

     

     

     

    스프링에 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;
        }

     

     해당 클래스 필드에 넘어온 값이 바인딩 될 때  각 필드에 지정된 포맷애노테이션에 따라 포맷터가 적용이 된다.

    728x90
    반응형

    댓글

Designed by Tistory.