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
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 날짜
}
}
'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 |
댓글