데이터 바인딩 추상화 - PropertyEditor
spring이 제공해주는 org.springframework.validation.DataBinder
1. Data Binding이란?
- 기술적인 관점으로 보았을 때는 프로퍼티 값을 타겟 객체에 설정하는 기능이다
- 사용자 관점으로 보았을 때는 사용자 입력값을 애플리케이션 도메인 모델에 동적으로 변환해 넣어 주는 기능이다. 예를 들면 입력값은 대부분 String(문자열) 인데, 그 값을 도메인 객체가 가지고 있는 자료형 (int, boolean, long, Date ) 등 Event, Book 같은 도메인 타입으로 변환해서 넣어주는 기능이다.
- 즉 사용자가 입력한 문자열 값을 도메인 타입에 맞춰 변환하여 사용 하는 것을 데이터 바인딩 이라고 합니다
2. Spring Data Binding
- Spring은 다양한 인터페이스로 추상화해서 데이터 바인딩 기능을 제공한다
- 데이터 바인딩 인터페이스는 주로 웹 MVC에서 사용하지만 Web에만 특화되어 있지 않고 여러 곳에서 사용되는 spring의 핵심 기술 중 하나이다
- 가장 고전적인 예제를 보겠다
- Event 도메인 클래스
public class Event {
private Integer id;
private String title;
public Event(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return "Event{" +
"id=" + id +
", title='" + title + '\'' +
'}';
}
}
- EventController 컨트롤러
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EventController {
// GetMapping 애노테이션으로 Get Request를 /event/이벤트id로 받게된다
@GetMapping("/event/{event}")
//@PathVariable 애노테이션으로 {event}는 event 도메인 객체로 받아서 매개 변수에 넣어준다
// EX) www.TheWing.com/event/1 이렇게 입력했다고 가정하면 1인 부분인 이벤트 id를 Event 타입에 맞게 변환을 해야한다
public String getEvent(@PathVariable Event event) {
System.out.println(event);
return event.getId().toString();
}
}
- EventControllerTest 테스트 클래스
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@WebMvcTest
public class EventControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void getTest() throws Exception {
//get 요청 1을 보낸다
//status가 200으로 나오길 바란다
//content가 1이 나오길 바란다.
mockMvc.perform(get("/event/1"))
.andExpect(status().isOk())
.andExpect(content().string("1"));
}
}
3. PropertyEditor 인터페이스
특징
- Spring 3.0 이전 까지 DataBinder가 변환 작업하여 사용하던 인터페이스이다
- Spring이 제공하는 DataBinder 인터페이스를 통해 사용된다
- Thread-safe 하지 않는다
- 상태 정보(값)를 저장하고 있기 때문에 여러 쓰레드를 공유해서 사용하면 안된다.
- Bean scope에서 Singleton 빈으로 등록해서 사용하면 안된다. (Bean scope 에서 Thread scope로 사용하는 것이 그나마 낫다)
- Object와 String 간의 변환만만 가능하여 사용 범위가 제한적이다
1) PropertyEditor 구현 예제
import java.beans.PropertyEditorSupport;
public class EventEditor extends PropertyEditorSupport {
// 보통 PropertyEditorSupport를 상속하면 getAsText()와 setAsText() 이 두개를 구현하지만
// 필요한 것은 Text를 event로 구현하기 위함이다
// 값이 들어오는것은 String이지만 Integer로 변환해야한다
@Override
public String getAsText() {
Event event = (Event)getValue();
return event.getId().toString();
}
//
//Thread safe하지 않기 때문에 여러 쓰레드에 공유해서 사용하면 안된다
//그냥 빈으로 Component로 등록하면 안된다
//웬만해서 절대 사용하면 안된다
//
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(new Event(Integer.parseInt(text)));
}
}
PropertyEditor
인터페이스를 구현하게 되면 매우 많은 양의 메소드를 구현해야 하기 때문에PropertyEditorSupport
클래스를 상속받아 필요한 메소드만 구현할 수 있다. 그 중 예시에서는getAsText()
와setAsText()
메소드를 구현한다
PropertyEditor 구현체 사용시 주의사항
- 서로 다른 쓰레드에게 공유되기 때문에 Thread-safe하지 않다.
PropertyEditor
의getValue()
와setValue()
메소드로 공유되는 값은PropertyEditor
가 보유하고 있는 값이여서 값이 바뀔 위험이 있다. (원하는 값이 나오지 않음)- 일반적인 빈으로 Component로 등록하면 안된다
- Bean scope로 Thread scope 빈으로 사용하면 그나마 괜찮다. 그래도 Singleton scope던 Thread scope이던 빈으로 등록해서 사용하지 않는 것이 좋다.
2) Controller 에 등록
PropertyEditor
를 사용하려면@InitBinder
애노테이션을 사용해서Controller
에 등록하여 사용한다
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EventController {
@InitBinder
public void init(WebDataBinder webDataBinder) {
//Event class 타입을 처리할 PropertyEditor를 등록할 수 있다
webDataBinder.registerCustomEditor(Event.class, new EventEditor());
}
@GetMapping("/event/{event}")
public String getEvent(@PathVariable Event event) {
System.out.println(event);
return event.getId().toString();
}
}
@InitBinder
로Event
class
타입을 처리할PropertyEditor
를 등록할 수 있다- WebDataBinder는 DataBinder의 구현체 중 하나이다
- DataBinder의 PropertyEditor를 사용해서 String으로 받은 "1"을 Integer 1로 변환이 가능하다
테스트 결과
Event{id=1, title='null'}
결론
이러한 구현하는 방법 단점
- 상당히 편리하지 않다
- 구현 자체도 편리하지 않다
- Thread-safe하지 않아서 사용하기 힘들다
- Bean으로 등록해서 사용하기도 위험하다
다음 포스팅은 이러한 단점을 보완한 데이터 바인딩 추상화 : Converter와 Formatter 에 대해서 알아보겠다.
References
- 해당 포스팅은 백기선님의 인프런 - 스프링 프레임워크 핵심 기술 강의를 보고 정리한 자료입니다.
'Spring > Spring Core' 카테고리의 다른 글
[Spring] AOP(Aspect-Oriented Programming) (0) | 2020.11.06 |
---|---|
[Spring] SpEL(Spring Expression Language) (0) | 2020.11.05 |
[Spring] 데이터 바인딩 추상화 - Converter, Formatter (0) | 2020.11.05 |
[Spring] Validation 추상화 (0) | 2020.11.04 |
[Spring] Resource 추상화 (0) | 2020.11.04 |