지금까지 학습한 Converter는 입력과 출력 타입에 제한이 없는 범용 타입 변환 기능을 제공한다. 하지만 개발자는 문자를 다른 타입으로 변환하거나, 다른 타입을 문자로 변환하는 상황이 대부분이다.
ex) 1000이라는 int를 1,000으로 변경해주거나 날짜를 format에 맞게 변경하거나
Converter(객체 -> 객체)로 변경이 가능하고 Formatter는 문자에 특화되어 있는 기능이라고 생각하면 된다.
@Slf4j
public class MyNumberFormatter implements Formatter<Number> {
@Override
public Number parse(String text, Locale locale) throws ParseException {
log.info("text = {}, locale = {}", text, locale);
// 1,000 -> 1000
NumberFormat format = NumberFormat.getInstance(locale);
return format.parse(text);
}
@Override
public String print(Number object, Locale locale) {
log.info("object = {}, locale = {}", object, locale);
// 1000 -> 1,000
NumberFormat format = NumberFormat.getInstance(locale);
String numberString = format.format(object);
return numberString;
}
}
SpringBoot의 Formatter를 상속받아서 parse와 print를 상속받아서 작성한다.
class MyNumberFormatterTest {
MyNumberFormatter formatter = new MyNumberFormatter();
@Test
void parse() throws ParseException {
Number result = formatter.parse("1,000", Locale.KOREA);
assertEquals(result, 1000L);
}
@Test
void print() {
String result = formatter.print(1000, Locale.KOREA);
assertEquals(result, "1,000");
}
}
Test 코드를 작성해서 실행해보면
우리가 예상하는 결과로 잘 나오는 것도 확인할 수 있다. 이제 Formatter를 ConversionService에 등록해서 사용해보자!
public class FormattingConversionServiceTest {
@Test
void formattingConversionService(){
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
//컨버터 등록
conversionService.addConverter(new IpPortToStringConverter());
conversionService.addConverter(new StringToIpPortConverter());
//포맷터 등록
conversionService.addFormatter(new MyNumberFormatter());
//컨버터 사용
IpPort ipPort = conversionService.convert("127.0.0.1:8080", IpPort.class);
assertEquals(ipPort, new IpPort("127.0.0.1",8080));
//포맷터 사용
String convert = conversionService.convert(1000, String.class);
assertEquals(convert, "1,000");
}
}
다음과 같이 DefaultFormattingConversionService
를 사용하면 converter와 formatter 모두 등록해서 사용할 수 있는 것을 확인할 수 있다. 그 이유는 DefaultFormattingConversionService
는 ConversionService
를 상속받아서 사용하고 있기 때문에 그대로 사용할 수 있는 것이다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
//우선 순위 때문에 주석처리 formatter < converter 우선순위를 가지기 때문
// registry.addConverter(new StringToIntegerConverter());
// registry.addConverter(new IntegerToStringConverter());
registry.addConverter(new IpPortToStringConverter());
registry.addConverter(new StringToIpPortConverter());
registry.addFormatter(new MyNumberFormatter());
}
}
실제 프로젝트에 적용하기 위해서는 다음과 같이 적용하면 된다.
그럼 다음과 같이 formatter가 적용된 부분에는 ,
가 들어가서 찍혀있는 것을 확인할 수 있다.
Formatter Interface를 구현하고 있는 구현체들을 보면 다음과 같이 Spring에서 제공하는 수많은 Formatter를 볼수 있다.
@Controller
public class FormatterController {
@GetMapping("/formatter/edit")
public String formatterForm(Model model){
Form form = new Form(10000, LocalDateTime.now());
model.addAttribute("form", form);
return "formatter-form";
}
@PostMapping("/formatter/edit")
public String formatterEdit(Form form){
return "formatter-view";
}
@Data
@AllArgsConstructor
static class Form{
@NumberFormat(pattern = "###,###")
private Integer number;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;
}
}
spring에서 제공하는 @NumberFormat
과 @DateTimeFormat
를 사용하면 숫자와 날짜 데이터는 간단하게 패턴을 적용해서 사용할 수 있다.
ConversionService
는 @RequestParam
, @ModelAttribute
, @PathVariable
, ViewTemplate에서 사용할 수 있으며 HttpMessageConverter
에는 적용되지 않는다. HttpMessageConverter는 우리가 api로 통신할 때 사용되는 ResponseBody나 RequestBody에 사용되는 부분이다.