데이터 바인딩 추상화: PropertyEditor - KwangtaekJung/inflearn-spring-framework-essential GitHub Wiki

15. 데이터 바인딩 추상화: PropertyEditor

DataBinder

  • org.springframework.validation.DataBinder
  • 기술적인 관점: 프로퍼티 값을 타겟 객체에 설정하는 기능
  • 사용자 관점: 사용자 입력값을 애플리케이션 도메인 모델에 동적으로 변환해 넣어주는 기능.
    해석하자면: 입력값은 대부분 “문자열”인데, 그 값을 객체가 가지고 있는 int, long, Boolean, Date 등 심지어 Event, Book 같은 도메인 타입으로도 변환해서 넣어주는 기능.

PropertyEditor

  • PropertyEditor(https://docs.oracle.com/javase/7/docs/api/java/beans/PropertyEditor.html)
    • 스프링 3.0 이전까지 DataBinder가 변환 작업 사용하던 인터페이스
    • 쓰레드-세이프 하지 않음 (상태 정보 저장 하고 있음, 따라서 싱글톤 빈으로 등록해서 쓰다가는...)
      따라서 빈으로 등록해서 사용하지 말고 각자 Controller에서 @InitBinder를 이용하여 개별적으로 등록해서 사용한다. (글로벌하게 등록하는 방법도 있다.)
    • Object와 String 간의 변환만 할 수 있어, 사용 범위가 제한적 임. (그래도 그런 경우가 대부분이기 때문에 잘 사용해 왔음. 조심해서..)

스프링 웹 MVC 뿐 아니라 XML 설정 파일에 입력한 문자열을 bean이 가지고 있는 적절한 타입으로 변환해서 넣어줄 때도 사용하고 spEL에서도 사용한다.

적절한 바인더를 찾지 못할 경우에는 exception 발생함.
MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'com.example.inflearnspringframeworkessential.Event'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.example.inflearnspringframeworkessential.Event': no matching editors or conversion strategy found]

@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) {
        System.out.println("event = " + event);
        return event.getId().toString();
    }
}

PropertyEditor를 implements하면 구현해야할 메서드가 너무 많다. PropertyEditorSupport를 extends 하여 원하는 메서드만 구현한다.

//@Component  // Thread-safe 하지 않기 때문에 이렇게 빈으로 등록해서 사용하면 안된다!!!
public class EventPropertyEditor extends PropertyEditorSupport { 
  @Override 
  public String getAsText() { 
    return ((Event)getValue()).getTitle(); 
  } 

  @Override 
  public void setAsText(String text) throws IllegalArgumentException {  
    int id = Integer.parseInt(text); 
    Event event = new Event(); 
    event.setId(id); 
    setValue(event); 
  } 
}
@WebMvcTest
class EventControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Test
    public void getTest() throws Exception {
        mockMvc.perform(get("/event/1"))
                .andExpect(status().isOk())
                .andExpect(content().string("1"));
    }
}