Spring Ch 2. 추상화 - 데이터 바인딩 추상화(PropertyEditor, Converter, Formatter)


데이터 바인딩

  • Web Application을 개발할 수록 코드는 점점 복잡해지고 여러 스크립트의 객체와 화면으로 입력받는 데이터의 타입이 일치해지기 어려워진다.
  • 예를 들어, 웹에서 form 태그로 값을 입력받아 처리하고자 할 때 입력받는 값은 String이지만 실제 사용되는 데이터는 Date, Boolean 또는 정의된 클래스 객체의 타입으로 사용되며 이를 처리하는 코드를 작성해야한다.
  • 입력 받은 문자열을 객체에 맞는 데이터로 변환하는 작업을 간편하게 하기 위해 Spring에서는 데이터 바인딩 기능을 갖는 인터페이스를 추상화하여 제공하고 있다.

PropertyEditor

  • PropertyEditor는 Spring 3.0버전 전까지 사용되고 있던 데이터 바인딩 인터페이스이다.
  • Text-Object간의 변환을 수행할 수 있도록 getAsText, setAsText 메소드를 제공하고 있다.
  • 그러나 멀티 스레드로 동시에 접근이 이루어질경우 데이터가 변경되기 쉬워 싱글톤으로 사용할 수 없으며 controller에 등록해서 사용해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Event {
Integer id;
@NotEmpty
String title;
public Event(Integer id) {
super();
this.id = id;
}
public String getTitle() {return title;}
public void setTitle(String title) {this.title = title;}
public void setId(Integer id) {this.id = id;}
public Integer getId() {return id;}
@Override
public String toString() {
return "Event [id=" + id + ", title=" + title + "]";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class EventEditor extends PropertyEditorSupport{
@Override
public String getAsText() { //객체의 프로퍼티 값을 텍스트로 반환
Event event = (Event) getValue();
return event.getId().toString();
}
@Override
public void setAsText(String text) throws IllegalArgumentException{
//setValue가 Event(1)로서 Event.id=1의 객체로 생성을 해준다.
setValue(new Event(Integer.parseInt(text)));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
public class EventController {
//같은 패키지 내부에 클래스명 + Editor suppix가 붙으면 자동으로 property Editor 바인딩이 된다.
@InitBinder //기본적으로 InitBinder 어노테이션으로 데이터 바인딩을 등록한다.
public void Init(WebDataBinder webDataBinder) {//데이터 바인더 구현체중 하나
webDataBinder.registerCustomEditor(Event.class, new EventEditor());
}

//요청된 URL패턴 /event/{event} 중 일부 값({event}의 event)를 맵핑하여 객체 event생성.
@GetMapping("/event/{event}")
public String getEvent(@PathVariable Event event) {//@PathVariable 어노테이션은 URL경로에 변수를 추가하는 기능을 수행한다.
System.out.println(event);
return event.getId().toString();//리턴값으로 들어가는 것은 event객체의 id를 String으로 변환한 형태
}
}
  • 위의 코드를 예로 들어보자. /event/1의 Get요청이 발생한다면 Controller는 맵핑된 값으로 event객체를 생성하고자 할 것이다. 이때 property Editor의 setAsText 메소드가 발생하여 맵핑된 1이라는 텍스트를 사용자가 정의한 처리에 의해 객체의 프로퍼티로 저장하는 등 정상적인 객체로 변환하여 값을 저장해준다.

Converter

  • Generic타입으로 데이터 변환을 수행하는 인터페이스이다.
  • 특정 형변환을 위한 컨버터 클래스를 추가하여 단방향 데이터 바인딩을 수행한다.
  • PropertyEditor와 달리 Thread-safe하므로 빈으로 등록하여 사용할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//config에 등록하지 않아도 springboot가 자동으로 등록해준다.
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();
}
}
}

Formatter

  • Formatter는 PropertyEditor 인터페이스에 다국화 기능, Thread-Safe 를 추가한 인터페이스이다.
  • 기존 PropertyEditor와 같이 String과 Object간의 변환 기능을 제공하고 있다.
  • 텍스트로부터 객체를 생성하는 parse 메소드, 객체로부터 프로퍼티 값을 String형태로 제공하는 print 메소드를 Override하여 사용한다.
1
2
3
4
5
6
7
8
9
10
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();
}
}

ConversionService

  • ConversionService는 형 변환을 서비스 계층에서 수행할 수 있도록 도와주는 인터페이스이다.
  • Converter와 Formatter의 데이터 바인딩이 ConversionService를 통해서 이루어지며 ConversionService는 등록된 Converter 또는 Formatter를 사용하여 데이터 바인딩을 수행한다.
  • DefaultFormattingConversionService가 일반적으로 많이 호출되지만 웹 어플리케이션의 경우 이를 상속하여 만든 WebConversionService를 빈으로 등록하여 사용한다.
  • WebMvcConfigurer를 구현한 Web Configuration클래스에 등록하여 사용할 수 Converter와 Formatter를 사용할 수 있지만 스프링 부트는 이들을 자동으로 등록해주는 기능을 갖고 있다.
1
2
3
4
5
6
7
8
//Configuration에 등록하여 사용하는 방법
@Configuration
public class WebConfig implements WebMvcConfigurer{
@Override
public void addFormatters(FormatterRegistry formatterRegistry) {
formatterRegistry.addConverter(new EventConverter.StringToEventConverter());
}
}

댓글