스프링 핵심 기술-11.PropertyEditor/Converter와 Formatter(데이터 바인딩 추상화)


데이터 바인딩 추상화: PropertyEditor

데이터 바인딩?

기술적인 관점: 프로퍼티 값을 타겟 객체에 설정하는 기능
사용자 관점: 사용자 입력값을 애플리케이션 도메인 모델에 동적으로 변환해 넣어주는(할당하는) 기능

할당할 때 왜 바인딩이 필요하냐면
입력값은 대부분 “문자열”인데, 그 값을 객체가 가지고 있는 int, long, Boolean, Date 등 심지어 Event, Book 같은 도메인 타입으로도 변환해서 넣어주는 기능.

ex.문자열 : “20191207” -> 2019-12-07

스프링이 제공하는 DataBinder 인터페이스
(org.springframework.validation.DataBinder)

스프링 초기부터 있던 기능이며, 웹 MVC에서 주로 사용하지만,
Application Context XML 설정 파일의 문자열을 빈으로 등록해서 적절한 값으로 넣어주는 것,
스프링 Expression Language 등

즉, 데이터 바인딩 개념 자체는 웹 MVC만 특화된게 아니라,
여러 곳에서 쓰이는 스프링의 핵심 기술중에 하나이고,
이러한 데이터 바인딩에 관련된 기능을 여러 인터페이스로 적절히 추상화 되어 있다.

구현 방법 (고전적인 방식 - 스프링 3.0 이전)

PropertyEditor 를 사용하는 DataBinder

@Component
public class EventEditor extends PropertyEditorSupport {

    @Override
    public String getAsText() {
        return ((Event)getValue()).getTitle();
    }
    // 어떤 객체든 스트링으로만 변환한다. (return string)

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        int id = Integer.parseInt(text);
        Event event = new Event();
        event.setId(id);
        setValue(event);
    }
    // 문자열만을 받을 수 있다. (param string)
}

PropertyEditor
스프링 3.0 이전까지 DataBinder가 변환 작업 사용하던 인터페이스이다.
하지만 쓰레드-세이프 하지 않아서 굉장히 위험하다.
(상태 정보 저장 하고 있음, 따라서 싱글톤 빈으로 등록해서 쓰다가는 a스레드가 가져온 값을 b스레드가 쓴다던지..)
또한 Object와 String 간의 변환만 할 수 있어, 사용 범위가 제한적 이다.
(그래도 그런 경우가 대부분이기 때문에 잘 사용해 왔다고 한다.. + 조심해서..)

그래서 이런 불편함을 해소하고자 나온 것들이 Converter와 Formatter이다.

데이터 바인딩 추상화: Converter와 Formatter

구현 방법 (Converter)

Converter

  • 스레드세이프 하지 않고, 상태정보를 공유 하지 않아서 기존 PropertyEditor의 위험한 상태를 보완한 인터페이스이다.
  • S 타입을 T 타입으로 변환할 수 있는 매우 일반적인 변환기.
  • 상태 정보 없음 == Stateless == 쓰레드세이프
  • ConverterRegistry에 등록해서 사용
public class EventConverter{

    @Component // 빈으로 등록해도 상관이 없다. 상태정보 미공유
    public static class StringToEventConverter implements Converter<String, Event> { // 소스, 타겟 string -> event로 변환
        @Override
        public Event convert(String source) {
            Event event = new Event();
            event.setId(Integer.parseInt(source));
            return event;
        }
    }
    
    public static class EventToStringConverter implements Converter<String, Event> { // 소스, 타겟 string -> event로 변환
        @Override
        public String convert(Event source) {
            return String.valueOf(source.getId());
        }
    }
}

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

@Configuration
public class WebConfig implements WebMvcConfigurer {

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

아까와 똑같은데 상태정보 미공유 string -> object / object -> string

근데 사실 기본적으로 등록이 되어 있는 컨버터나 포메터들이 자동으로 변환을 해 준다.
스프링이 기본적으로 제공해주지 않는 것들만 직접 구현해주면 된다..

ex.지금 사용하는 예제의 경우
@GetMapping(“/event/{event}”)
이런 테스트 케이스를
/event/1로 던지는데 이 받은 1을 id 세팅해 주는 예제이다.
근데 대부분은 이름 맞춰 던져주고 스프링이 기본적으로 세팅을 해 주기 때문에
직접 구현이 필요한 것만 구현해주면 될 것 같다.

구현 방법 (Formatter - 웹 쪽 특화)

Formatter

웹 쪽은 문자열으로 많이 들어오고 내보내는 것도 문자열로 많이 보낸다.
앞서 Message Source에서 살펴본 처럼 다국화하는 기능들을 추가적으로 제공하는 좀 더 웹쪽에 특화되어있는 인터페이스를 제공하는 것이 Formatter이다.

  • PropertyEditor 대체제
  • Object와 String 간의 변환을 담당한다.
  • 문자열을 Locale에 따라 다국화하는 기능도 제공한다. (optional)
  • FormatterRegistry에 등록해서 사용
public class EventFormatter implements Formatter<Event> {
    @Override
    public Event parse(String text, Locale locale) throws ParseException {
        Event event = new Event();
        int id = Integer.parseInt(text);
        event.setId(id);
        return event;
    }
    @Override
    public String print(Event object, Locale locale) {
        return object.getId().toString();
    }
}

마찬가지로 등록을 해 주어야 한다.(FormatterRegistry)

@Configuration
public class WebConfig implements WebMvcConfigurer {

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

기본 제공되는 AddressFormatter(주소포멧) 등.. 사용할 수도 있다.

@Override
public void addFormatters (FormatterRegistry registry) {
    AddressFormatter addressFormatter = new AddressFormatter();
    addressFormatter.setStyle(AddressFormatter.Style.REGION);
    registry.addFormatter(addressFormatter);
}

ConversionService

앞서 살펴본 Converter나 Formatter의 경우 ConversionService에 등록되어서 사용되는 것.

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

  • 실제 변환 작업은 이 인터페이스를 통해서 쓰레드-세이프하게 사용할 수 있음.
  • 스프링 MVC, 빈 (value) 설정, SpEL에서 사용한다.
  • DefaultFormattingConversionService
    1. FormatterRegistry 구현 (사실 FormatterRegistry 가 ConverterRegistry를 상속)
    2. ConversionService 구현
    3. 여러 기본 Converter의와 Formatter 등록 해 줌.
      즉, Formatter와 ConversionService 역할을 모두 한다.

스프링 부트의 경우
웹 애플리케이션인 경우에 DefaultFormattingConversionSerivce를 상속하여 만든 WebConversionService를 빈으로 등록해 준다.
(DefaultFormattingConversionSerivce 에서 기능 확장)

  • Formatter와 Converter 빈을 찾아 자동으로 등록해 준다. (Formatter, Converter가 먼저 빈으로 등록이 되어 있어야 한다.)
  • default format, data format, JODA time, money 세팅 등

기본 등록되는 빈

@Autowired 로 받아서 conversionService 를 출력하면 기본 등록되는 빈들 확인 가능.

  • 참고
    Junit 테스트를 진행할 때 내가 필요한 빈들을 명시적으로 선언해 사용하는 방법 추천.
    @ExtendWith(SpringExtension.class) // junit5 기준 -> junit4 에서는 RunWith 사용
    @WebMvcTest({EventController.class, EventValidator.class})
    public class EventControllerTest {
    





© 2019. by jaeuk