1. 포맷터(Fomatter)란?
지난 글에서 학습한 Converter의 세부적인 개념이다. 특히나 금액, 날짜와 같은 자주 쓰는 타입에 대해 지역 정보(Locale)를 바탕으로 객체 <-> 문자 간 특화된 변환을 지원한다.
스프링에서 제공하는 Formatter를 살펴보면 Printer, Parser 객체를 상속받는 것을 볼 수 있다. 각각의 메서드 중 print 메서드는 객체를 문자로 변경하고, parse 메서드는 문자를 객체로 변경하는 역할을 한다.
2. Formatter 사용
직접 사용해보기
Formatter를 사용하는 기본적인 방법에 대해 알아본다. 테스트 코드로 위에서 다룬 print, parse 메서드를 연습한다.
org.springframework의 Formatter를 상속받고, 타입은 Number를 넣어준다. Converter와 다르게 Formatter는 두 변환 타입 중 한 가지는 String으로 고정되어 있기 때문이다. 참고로 Number 객체는 Integer, Double 등 숫자 타입들의 부모 객체이다. NumberFormat.getInstance로 파라미터로 받아온 String 또는 Number 객체를 NumberFormat으로 변환해준다. 이후 parse, format 메서드를 통해 각각 객체, 문자열로 변환해준다.
java/hello/typeconverter/formatter/MyNumberFormatter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Slf4j
public class MyNumberFormatter 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);
return NumberFormat.getInstance(locale).format(object);
}
}
|
cs |
테스트 코드를 보면, formatter는 천 단위의 숫자에 콤마(,)가 찍힌 문자열을 Long 타입의 숫자로 변환해주고, 숫자를 콤마가 찍힌 문자열로 변환해준다. 이것은 Locale 정보를 KOREA로 지정해서 그런 것이고, 만약 두 자리 단위로 콤마를 찍는 나라가 있다면, 그에 맞춰서 알맞은 위치에 콤마를 찍어준다. 이런 기능을 통해 실제 클라이언트에 숫자에 대한 정보를 전달할 때는 좀 더 깔끔한 형식(format)으로 변환하여 문자열로된 정보를 제공하고, 받을 수 있게 된다. 뒤에서는 날짜 정보에 대한 형식에 대한 변환 기능도 실습해볼 것인데, 이런 편리한 기능을 제공하는 것이 Formatter라고 생각하면 된다.
java/hello/typeconverter/formatter/MyNumberFormatterTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class MyNumberFormatterTest {
MyNumberFormatter formatter = new MyNumberFormatter();
@Test
void parse() throws ParseException {
Number result = formatter.parse("1,000", Locale.KOREA);
assertThat(result).isEqualTo(1000L);
}
@Test
void print() {
String result = formatter.print(1000, Locale.KOREA);
assertThat(result).isEqualTo("1,000");
}
}
|
cs |
스프링 객체에 등록 후 사용해보기 : DefaultFormattingConversionService
Formatter도 Converter의 일종이므로, 스프링에서 사용이 필요하면 ConversionService, ConversionRegistry를 상속받고 있는 DefaultFormattingConversionService 에 Converter 및 Formatter를 등록하면 된다. 실제 스프링은 이 클래스를 상속받은 WebConversionService를 내부에서 사용한다.
java/hello/typeconverter/formatter/FormattingConversionServiceTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class FormattingConversionServiceTest {
@Test
void formattingConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
//컨버터 등록
conversionService.addConverter(new StringToIpPortConverter());
conversionService.addConverter(new IpPortToStringConverter());
//포맷터 등록
conversionService.addFormatter(new MyNumberFormatter());
//컨버터 사용
IpPort ipPort = conversionService.convert("127.0.0.1:8080", IpPort.class);
assertThat(ipPort).isEqualTo(new IpPort("127.0.0.1", 8080));
//포맷터 사용
assertThat(conversionService.convert(1000, String.class)).isEqualTo("1,000");
assertThat(conversionService.convert("1,000", Long.class)).isEqualTo(1000L);
}
}
|
cs |
3. 스프링에 Formatter 적용
Formatter 등록 후 사용
기존 Converter 테스트 시에, FormatterRegistry를 이용했으므로, 새로 만든 Formatter만 등록해주면 된다. 다만, String을 숫자로 변환하던 Converter는 주석처리 해주도록 한다. Formatter에 비해 Converter가 우선순위를 갖기 때문이다.
java/hello/typeconverter/WebConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToIpPortConverter());
registry.addConverter(new IpPortToStringConverter());
//Converter 우선 순위 적용 때문에 주석처리
// registry.addConverter(new StringToIntegerConverter());
// registry.addConverter(new IntegerToStringConverter());
//추가
registry.addFormatter(new MyNumberFormatter());
}
}
|
cs |
서버 -> 클라이언트
"/converter-view" 페이지로 접속해보면, Formatter 가 잘 적용되어 콤마가 찍힌 숫자로 렌더링 되는 것을 볼 수 있다.
클라이언트 -> 서버
"/hello-v2?date=10,000,000" 으로 요청을 보내면, MyNumberFormatter가 동작하여 문자를 숫자 타입으로 변환한다.
스프링 제공 Formatter, 어노테이션 사용
Formatter 클래스의 구현체를 보면, 숫자나 날짜 타입을 변환해주는 수많은 Formatter가 이미 기본적으로 제공되는 것을 확인할 수 있다. 그러나, Formatter는 형식이 지정되어 있기 때문에 여러 개의 필드에 각기 다른 타입의 Formatter를 지정하기가 불편하다. 이를 보완하기 위해서 어노테이션 기반의 Formatter가 제공된다.
아래 코드와 같이 Controller에 @NumberFormat, @DateTimeFormat 이라는 어노테이션으로 포맷을 지정할 수 있다. 이렇게 되면 Controller에서 자동으로 해당 양식으로 데이터를 주고 받게 된다.
java/hello/typeconverter/controller/FormatterController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
@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;
}
}
|
cs |
결과화면
Form 양식에 패턴이 적용되고, View 화면에서도 이중 중괄호가 적용된 부분은 컨트롤러에서 지정한 패턴대로 출력됨을 확인할 수 있다.
4. 유의사항
ConversionService는 HttpMessageConverter와 전혀 상관이 없다. HttpMessageConverter는 HTTP 메시지의 body 정보를 JSON 등의 객체로 변환해주는 것이며, 스프링 내부적으로는 Jackson과 같은 라이브러리를 사용한다. 따라서 이런 정보들의 타입을 변환하고 싶다면, @JsonProperty와 같은 해당 라이브러리에서 제공하는 타입 컨버터를 사용해야 한다.
ConversionService는 @RequestParam, @ModelAttribute, @PathVariable, 뷰 템플릿 등에 적용할 수 있다.
참조
1. 인프런_스프링 MVC 2편 - 백엔드 웹개발 핵심 기술_김영한 님 강의
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2
'Programming-[Backend] > Spring' 카테고리의 다른 글
[스프링 웹MVC-2] 21. 업로드, 다운로드 기능 구현 예제 (0) | 2021.09.22 |
---|---|
[스프링 웹MVC-2] 20. 파일 업로드 (0) | 2021.09.21 |
[스프링 웹MVC-2] 18. 타입 컨버터 - ConversionService (2) | 2021.09.15 |
[스프링 웹MVC-2] 17. API 예외 처리 - 스프링 ExceptionResolver (0) | 2021.09.13 |
[스프링 웹MVC-2] 16. API 예외 처리 - 스프링부트 기본 오류 처리, HandlerExceptionResolver (0) | 2021.09.09 |