스프링 핵심 기술-11.PropertyEditor/Converter와 Formatter(데이터 바인딩 추상화)
in Back-end on Spring, Propertyeditor, Data binding, Databinder, 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
- FormatterRegistry 구현 (사실 FormatterRegistry 가 ConverterRegistry를 상속)
- ConversionService 구현
- 여러 기본 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 {