[Spring] 스프링 Converter와 Formatter를 이용한 데이터 바인딩(Converter, Formatter : Spring Data Binding)

| Converter와 Formatter


PropertyEditor는 스프링 초창기에 썼던 데이터 바인딩 인터페이스였지만 후에 이를 대체할 ConverterFormatter란 데이터 바인딩 인터페이스가 나오게 되었다.


Converter는 S타입을 T 타입으로 변환할 수 있는 일반적인 변환기이며 Thread-safe하다. FormatterObjectString간의 변환을 담당하며 문자열을 Locale에 따라 다국화하는 기능을 제공한다. 웹 어플리케이션을 제작할 때는 주로 Formatter를 사용한다.


| Converter 구현


Converter는 제네릭으로 두 개의 인자, SourceTarget을 받아서 구현한다. 또한 ConverterPropertyEditor와는 다르게 Thread-safe하기 때문에 스프링 빈으로 등록해서 사용 가능하다.

public class EventConverter {

public static class StringToEventConverter implements Converter<String, Event> {
@Override
public Event convert(String source){
return new Event(Integer.parseInt(source));
}
}

public static class EventToStringConverter implements Converter<Event, String>{
@Override
public String convert(Event source){
return source.getId().toString();
}
}
}


위에서 작성했던 Converter를 등록하려면 스프링MVC에서는 WebMvcConfigurer 인터페이스를 구현한 클래스를 작성해야 한다. WebMvcConfigurer 인터페이스의 addFormatters 메서드를 오버라이딩하여 Converter를 등록할 수 있다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new EventConverter.StringToEventConverter());
}
}


| Formatter 구현


Formatter는 제네릭으로 한 개의 인자만을 받는다. 왜냐하면 ObjectString간의 변환을 담당하기 때문에 다른 하나의 인자는 String으로 정해져 있기 때문이다.

public class EventFormatter implements Formatter<Event> {

@Override
public Event parse(String text, Locale locale) throws ParseException {
return new Event(Integer.parseInt(text));
}

@Override
public String print(Event object, Locale locale) {
return object.getId().toString();
}
}


Converter와 마찬가지로 WebMvcConfigurer 인터페이스를 구현한 클래스에 Formatter를 등록할 수 있다. 

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new EventFormatter());
}
}


| ConversionService


ConverterFormatter 이 두 데이터 바인딩 인터페이스는 위에서 WebMvcConfigurer의 메서드를 통해 ConversionService에 등록된다. ConversionService는 실제 데이터 변환이 일어나는 곳이며 이 ConversionService를 통해 데이터 바인딩이 일어나게 된다. 참고로 스프링 부트에서는 ConversionServiceWebConversionService 스프링 빈으로 자동 주입된다. (WebConversionServiceDefaultFormattingConversionService를 상속받은 클래스)

@Component
public class AppRunner implements ApplicationRunner {

@Autowired
ConversionService conversionService;

@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(conversionService.getClass().toString());
}
}

class org.springframework.boot.autoconfigure.web.format.WebConversionService


| WebConversionService


스프링 부트가 제공하는 클래스인 WebConversionServiceFormatterConverter를 자동으로 등록해준다. 따라서 위에 ConverterFormatter를 등록하기 위해 만들었던 WebMvcConfigurer를 작성할 필요가 없다. (즉, FormatterConverter를 스프링 빈으로 등록하면 자동적으로 ConversionService에 등록되게 된다)

@Component
public class EventFormatter implements Formatter<Event> {

@Override
public Event parse(String text, Locale locale) throws ParseException {
return new Event(Integer.parseInt(text));
}

@Override
public String print(Event object, Locale locale) {
return object.getId().toString();
}
}
public class EventConverter  {

@Component
public static class StringToEventConverter implements Converter<String, Event> {
@Override
public Event convert(String source){
return new Event(Integer.parseInt(source));
}
}

@Component
public static class EventToStringConverter implements Converter<Event, String>{
@Override
public String convert(Event source){
return source.getId().toString();
}
}
}
@RestController
public class EventController {

@GetMapping("/event/{event}")
public String getEvent(@PathVariable Event event){
System.out.println(event);
return event.getId().toString();
}
}


아래의 테스트는 EventFormatter를 스프링 빈으로 등록하여 테스트를 진행하는 코드다. 위에서 언급했던 코드를 정상적으로 작성했다면 아래 테스트를 무리없이 통과할 수 있을 것이다.

@RunWith(SpringRunner.class)
@WebMvcTest({EventFormatter.class, EventController.class})
public class EventControllerTest {

@Autowired
MockMvc mockMvc;

@Test
public void getTest() throws Exception {
mockMvc.perform(get("/event/1"))
.andExpect(status().isOk())
.andExpect(content().string("1"));
}
}





이 글을 공유하기

댓글(2)

  • brian
    2019.02.28 12:20

    설명 엄청 깔끔하시네요. 다른 포스트도 감사히 잘 읽겠습니다.

    • 2019.03.06 12:45 신고

      오 댓글 달아주셔서 감사합니다^^ 도움 되셨다니 다행이네요. 요즘 바빠서 제대로 포스팅 못하고 있는 데 다른 글들도 꾸준히 올릴게요 ㅎㅎ

Designed by JB FACTORY