- [Spring] DataBinding추상화,Converter,Formatter란? 목차
데이터 바인딩이란?
-사용자가 입력한 값을 어플리케이션에서 사용하는 도메인으로 매핑하는 기능을 일컫습니다.
-Spring MVC에서 Controller 안의 Mapping 메소드들에서 사용자가 전달한 값을 자동으로 매개변수에 입력된 Command 객체로 변환되는 것도 이 데이터바인딩을 통해서 가능한 것입니다.
데이터 바인딩 필요 이유 why?
왜 바인딩이 필요한가?
-사용자가 입력한 값은 서버에서 기본적으로 String으로 인식을 합니다. HttpServletRequest 객체의 getParameter() 메소드가 반환하는 값이 String인 것을 보면 알 수 있습니다. 이 때문에 서버 프로그램에서 이 데이터를 상황에 맞게 처리하기 위해서는 특정 객체로 맵핑을 할 필요가 있습니다. 간단히 예제를 통해 알아보도록 하겠습니다.
PropertyEditor 예제
우선 가장 원초적인 방법인 PropertyEditor를 이용해 데이터바인딩 하는 방법을 알아 보겠습니다.
Event
public class Event {
private Integer id;
public Event() { }
public Event(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
우선 사용자의 입력값이 맵핑될 Command Class로 사용될 Event Class 입니다. 간단하게 하기 위해 사용자로 부터 입력되는 Id만을 가지도록 했습니다.
EventController 1
@RestController
public class EventController {
@GetMapping("/event/{event}")
public String getEvent(@PathVariable("event") Event event){
return event.getId().toString();
}
}
EventController에서는 GetMapping을 통해 사용자가 /event/{event} 형태로 요청을 하면 {event}값을 Event 객체로 맵핑하여 응답으로 Body에 event객체의 id를 입력하여 반환하도록 했습니다.
EventControllerTest
@RunWith(SpringRunner.class)
@WebMvcTest
public class EventControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void getEventTest() throws Exception{
mockMvc.perform(get("/event/1"))
.andExpect(status().isOk())
.andExpect(content().string("1"));
}
}
간단하게 Controller를 Test하기 위한 class입니다. /event/1의 형태로 요청을 하여 응답으로는 OK가, Response body에는 사용자가 요청한 값인 1이 들어있도록하여 테스트를 해보겠습니다.
결과는 .. 위와 같이 String 을 Event 타입으로 변형할 수 없다는 Error를 내뱉고 Test에 실패하게 됩니다. 사용자가 입력한 값인 1자체를 Event객체로 변환할 수 없기 때문에 나타나는 에러입니다.
이런 경우 우리는 데이터바인딩을 통해 해당 값을 Event 객체로 변환할 수 있도록 Spring에 방법을 알려주어야 합니다.
EventEditor
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 {
Event event = new Event();
event.setId(Integer.parseInt(text));
setValue(event);
}
}
PropertyEditorSupport 를 상속받은 클래스에 getAsText(), setAsText() 메소드를 오버라이딩하여 맵핑 방법을 직접 기술해보도록 하겠습니다. 이름을 보면 알 수 있듯이, getAsText()의 경우 특정 객체를 String으로 바인딩하는 것이며, setAsText()의 경우 String을 이용해 특정 객체로 바인딩하는 것입니다.
중요한 점은 value를 공유하기 때문에 ThreadSafe하지 않다는 것입니다. 때문에 해당 클래스를 Bean으로 등록하여 사용할 수 없습니다. 그럼 어떻게 사용할까요 아래를 보시죠.
EventController 2
@RestController
public class EventController {
@InitBinder
public void init(WebDataBinder webDataBinder){
webDataBinder.registerCustomEditor(Event.class, new EventEditor());
}
@GetMapping("/event/{event}")
public String getEvent(@PathVariable("event") Event event){
return event.getId().toString();
}
}
다시 EventController입니다. 상단에 보시면
@InitBinder 어노테이션이 부여된 init()메소드를 볼 수 있습니다. 매개변수로 WebDatabinder가 전달되며
이 객체의 registerCustomEditor()메소드에 매개변수로 우리가 생성한 Editor를 넘겨주면 끝입니다. 다시 Test를 돌려보죠.
성공적으로 Test를 통과했습니다.
단점
- 직접 CustomEditor를 작성해야하는데 방법이 매우 불편하다.
- value를 공유하기 때문에 ThreadSafe하지 않다.
- ThreadSafe하지 않기 때문에 Bean으로 등록할 수 없다.
- Object와 String 간에만 상호 변환이 가능하다.
Converter 예제
위에서 EvnetEditor를 이용해 데이터바인딩을 하는 방법을 알아보았습니다. 하지만 직접 Editor를 작성하는 것도 불편하며, ThreadSafe하지 않고, 그 때문에 Bean으로 등록하지 못해서 InitBinder를 통해 직접 등록하여 사용해야하는 불편함이 있었습니다. 이번에는 Spring3.0 이후 추가된 Converter를 이용해 데이터바인딩을 하는 방법을 알아보겠습니다. Converter는 ThreadSafe하며 그 때문에 Bean으로 등록이 되고, Object와 String 간 뿐만아니라 여러 타입간의 데이터바인딩도 지원을 합니다.
EventConverter
public class EventConverter {
@Component
public static class StringToEvent implements Converter<String, Event> {
@Override
public Event convert(String source) {
return new Event(Integer.parseInt(source));
}
}
@Component
public static class EventToString implements Converter<Event, String>{
@Override
public String convert(Event source) {
return source.getId().toString();
}
}
}
데이터 바인딩을 위한 EventConverter Class를 생성하고 Converter 인터페이스를 구현하였습니다.
<>안에는 <변환되는객체, 변환될 목표 객체> 의 형식으로 입력해주면 됩니다. 그 후 오버라이딩 된 메소드의 반환형에 맞게 작성해주면 끝입니다. 또한 이렇게 작성한 클래스는 상태정보를 가지지 않으므로 Bean으로 등록해 사용할 수도 있습니다.
WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new EventConverter.StringToEvent());
}
}
사용법은 간단합니다. Java Config파일을 만들기 위해 Class 파일을 하나 생성한 후 WebMvcConfigurer을 구현합니다. 그 후 addFormatters()라는 메소드를 구현하고 매개변수로 전달되는 registry를 이용해 위에서 작성한 Converter를 등록하기만 하면 됩니다.
이번에도 성공적입니다.
Formatter 예제
이번에는 Formatter라는 것을 사용해서 데이터 바인딩을 해보겠습니다. Formatter의 경우에는 조금더 Web 프로그래밍에 최적화된 인터페이스라고 할 수 있습니다. Web의 경우 사용자의 요청값은 보통 String이며 응답 값도 String으로 해주게 됩니다. 그 때문에 Formatter는 Object와 , String간의 변경을 수월하게 할 수 있도록 , ThreadSafe 하고, 입력 받은 문자열에 따른 Message를 반환해주는 MessageSource를 사용할 수도 있습니다.
EventFormatter
Converter를 생성하는 방법과 비슷합니다. 다만 이번에는 String->Object, Object->String 으로 각각 변환을 담당할 Converter들을 생성할 필요없이 하나의 Formatter Class 만을 생성하여 Formatter Interface를 구현합니다.
또한 <>에는 String 과 Object 간의 변환만을 담당할 것이므로 목표 Object만을 담아주면 됩니다. 훨씬 직관적이고 수월해진 것 같습니다. 특별한 점은 위에서 말씀드린 MessageSource를 이용한 다국화가 가능하다는 것입니다. 매개변수로 전달되는 locale을 이용해 원한는 메시지를 가져올 수 있습니다.
WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new EventFormatter());
}
}
사용도 간단합니다. 앞서 사용한 WebConfig에서 registry객체가 가진 method중 addFormatter()의 매개변수로 생성한 EventFormatter()를 넘겨주기만 하면 됩니다.
ConversionService
위에서 알아본 데이터 바인딩 작업중 처음에 알아본 PropertyEditor의 경우 DataBinder를 사용했지만,Converter, Formatter는 ConversionService에 등록이 되며 ConversionService에 의해 Convert(변환작업)를 하게 됩니다.
DefaultConversionService
ConversionService 구현체중 DefaultConversionService라는 Class가 있습니다. DefaultConversionService의 경우 두가지를 모두 구현 했다고 표현이 되어있는데 실제로는 조금더 복잡한 구조로 이루어져 있습니다. 조금 더 자세히 말씀드리면 ConversionService, ConverterRegistry라는 두가지 인터페이스를 상속받은 ConfiguableConversionService인터페이스를 다시 구현한 구현체를 상속받은 Class입니다. 따라서 이 클래스는 다음의 두가지 기능을 가집니다.
- FormatterRegistry : Formatter(Converter)를 등록하는 기능
- ConversionService : Convert를 수행하는 기능
또한 위 그림에서 알 수 있듯이 FormatterRegistry의 경우 ConverterRegistry를 상속하고 있기 때문에 FormatterRegistry를 통해서도 Converter를 등록할 수 있습니다.
사용법
사용법은 간단합니다. 앞서서 Formatter, Converter를 사용했던것 처럼 Registry를 이용해 등록하는 과정이 필요없습니다. 즉, Web관련 Configure클래스가 필요없다는 것 입니다. 우선 Formatter를 생성합니다.
Formatter
@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();
}
}
앞서 생성했던 Formatter와 같지만 이번에는 Bean으로 등록하기 위해 @Component어노테이션을 부여했습니다.
Controller
@RestController
public class EventController {
@Autowired
ConversionService conversionService;
@GetMapping("/event/{event}")
public String getEvent(@PathVariable("event") Event event){
System.out.println(event);
return event.getId().toString();
}
}
Controller에서는 ConversionService를 사용하기 위해 @Autowired어노테이션을 부여합니다. SpringBoot를 사용하고 있다면 자동으로 WebConversionService가 주입이 됩니다. 이것은 SpringBoot의 기능이지만 빈으로 등록된 Converter와 Formatter의 경우 자동으로 ConversionService에 등록이 됩니다.
결과
'Spring' 카테고리의 다른 글
[Spring] Lombok 이란? (0) | 2020.01.14 |
---|---|
[Spring]Resource 추상화 (0) | 2020.01.14 |
[Spring] Validation이란? (0) | 2020.01.13 |
[Spring] ApplicationEventPublisher란? (0) | 2020.01.13 |
[Spring] Environment 프로파일이란? (0) | 2020.01.13 |