본문 바로가기
Spring|Spring-boot

Spring Formatter

by oncerun 2022. 1. 10.
반응형

Converter를 등록하려고 보면 사용하는 interface를 보면 FormatterRegistry 타입을 통해 등록한다.

 

Converter는 사실 입력과 출력 타입에 제한이 없다. Converter <Object, Object>로 구현한다면 모든 타입을 받고 원하는 타입으로 캐스팅하여 처리할 수 있기 때문이다. 일반적인 웹 애플리케이션은 문자를 다른 타입으로 변환하는 기능이 정말 많다.

 

예시를 들자면 가격, 월급, 수량을 확인하면 천의 자리마다 , 로 구분되어 찍힌다. 또는 날짜를 문자로 그 반대로 문자를 날짜로 변경하는 경우가 상당히 많고 Locale현지화 정보 또한 정말 많이 사용한다.

 

이렇게 객체를 특정한 포맷에 맞추어 문자로 출력하거나 그 반대로 문자를 객체로 입력하는 기능에 특화된 것이 바로 Formatter이다.

 

 

스프링의 Formatter

public class MyNumberFormatter implements Formatter<Number> {


    @Override
    public Number parse(String text, Locale locale) throws ParseException {
        return null;
    }

    @Override
    public String print(Number object, Locale locale) {
        return null;
    }
}

 

Formatter은 입력 혹은 출력 중 타입이 String으로 고정이기 때문에 implements Formatter<변환타입>으로 사용한다.

 

 

@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);
}

 

java.text.NumberFormat은 모든 숫자 형식의 기본 클래스로 숫자 형식을 가진 로케일과 로케일의 이름을 결정하는 방법을 제공합니다.  인스턴스를 가져올 때 인자로 locale을 제공해주면 해다 locale로 숫자를 변환할 수 있습니다.

 

@Test
void parse() throws ParseException {

    Number number = formatter.parse("1,000", Locale.KOREA);
    assertThat(number).isEqualTo(1000L);

}

22:01:08.243 [main] INFO hello.typeconverter.formatter.MyNumberFormatter - text = 1,000  , locale = ko_KR

* Long 타입 사용

 

 

 

반대로 Number를 String으로 변경해 봅니다.

@Override
public String print(Number object, Locale locale) {
    log.info("object = {} , object = {}", object, locale);
    return NumberFormat.getInstance(locale).format(object);

}
@Test
void print() {

    String result = formatter.print(1000, Locale.KOREA);
    assertThat(result).isEqualTo("1,000");

}

22:01:08.362 [main] INFO hello.typeconverter.formatter.MyNumberFormatter - object = 1000 , object = ko_KR

 

format을 통해 숫자를 문자로 포맷할 수 있습니다.

 

추가적으로 US 달러의 통화의 문자열이 들어온 경우 Formatter를 통해 간단하게 숫자로 변경할 수도 있습니다.

public Number currencyParse(String text, Locale locale) throws ParseException {
    NumberFormat formatter = NumberFormat.getCurrencyInstance(locale);
    Number parse = formatter.parse(text);
    log.info("text : {}, locale : {}  parse : {}" , text, locale, parse);
    return parse;
}
@Test
void currencyParse() throws ParseException {
    Number number = formatter.currencyParse("$10,000", Locale.US);
    System.out.println("number = " + number);
}

22:16:31.058 [main] INFO hello.typeconverter.formatter.MyNumberFormatter - 

text : $10,000, locale : en_US  parse : 10000
number = 10000

 

 

즉 Formatter는 String에 대한 상대적인 타입 변환과 Locale기능이 추가된 특별한 Converter라고 할 수 있습니다.

 

자세한 내용은 공식 문서를 참고하자. 

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#format

 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io

 

 

FormattingConversionService는 포맷터를 지원하는 컨버전 서비스이다.

DefaultFormattingConversionService는 FormattingConversionService에 기본적인 통화, 숫자 관련 몇 가지 기본 포맷터를 추가해서 제공한다

 

DefaultFormattingConversionService 상속 관계

FormattingConversionService 는 ConversionService 관련 기능을 상속받기 때문에 결과적으로 컨버터도 포맷터도 모두 등록할 수 있다.

그리고 사용할 때는 ConversionService 가 제공하는 convert를 사용하면 된다.

추가로 스프링 부트는 DefaultFormattingConversionService 를 상속받은 WebConversionService를 내부에서 사용한다.

 

 

등록

@Configuration
public class Config implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToIpPortConverter());
        registry.addConverter(new IpPortToStringConverter());
        //registry.addConverter(new StringToIntegerConverter());
        //registry.addConverter(new IntegerToStringConverter());
        registry.addFormatter(new MyNumberFormatter());

    }
}

 

* Formatter보다 Converter가 우선순위가 더 높기 때문에 동일역할하는 컨버터는 주석 처리했다.

 

 

스프링이 제공하는 기본 포맷터


스프링은 자바에서 기본으로 제공하는 타입들에 대해 수많은 포맷터를 기본으로 제공한다.


그런데 포맷터는 기본 형식이 지정되어 있기 때문에, 객체의 각 필드마다 다른 형식으로 포맷을 지정하기는
어렵다.


스프링은 이런 문제를 해결하기 위해 애노테이션 기반으로 원하는 형식을 지정해서 사용할 수 있는 매우
유용한 포맷터 두 가지를 기본으로 제공한다.


@NumberFormat : 숫자 관련 형식 지정 포맷터 사용, NumberFormatAnnotationFormatterFactory
@DateTimeFormat : 날짜 관련 형식 지정 포맷터 사용, Jsr310 DateTimeFormatAnnotationFormatterFactory


@Controller
public class FormatterController {

    @GetMapping("/formatter/edit")
      public String formatterForm(Model model) {
      Form form = new Form();
      form.setNumber(10000);
      form.setLocalDateTime(LocalDateTime.now());
      model.addAttribute("form", form);
      return "formatter-form";
    }

     @PostMapping("/formatter/edit")
       public String formatterEdit(@ModelAttribute Form form) {
       return "formatter-view";
     }
     
   @Data
   static class Form {
   
     @NumberFormat(pattern = "###,###")
     private Integer number;

     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime localDateTime;  //java 8 날짜
   }
}

 

공식문서

 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io

 

반응형

'Spring|Spring-boot' 카테고리의 다른 글

Spring-boot ExceptionResolver  (0) 2022.01.15
Spring-boot Error Page  (0) 2022.01.13
Spring Converter  (0) 2022.01.10
Spring MessageCodesResolver  (0) 2022.01.08
Spring Web Validation (2)  (0) 2022.01.08

댓글