Gidhub BE Developer

Spring 프레임워크 핵심 기술 - 데이터 바인딩 추상화/ PropertyEditor

2019-09-02
goodGid

이 글의 코드 및 정보들은 강의를 들으며 정리한 내용을 토대로 작성하였습니다.

데이터 바인딩이란?

  • 사용자가 입력한 값이

  • 어플리케이션 도메인 객체에

  • 동적으로 할당되는 과정을 뜻한다.

  • 그리고 데이터 바인딩 개념은

  • 스프링뿐만 아니라 다른 곳에서도 사용되는 개념이다.

동적 할당이 필요한 이유는?

  • 사용자는 주로 문자열을 입력하고

  • 어플리케이션 도메인에는

  • 다양하나 타압들의 객체들이 존재한다.


  • 그러므로

  • 사용자가 입력한 문자열을

  • 객체가 갖고있는

  • 다양한 타입으로 변환을 시켜야하기 때문에

  • 동적 할당이 필요하다.



PropertyEditor

  • 스프링 3.0 이전까지

  • DataBinder가 변환 작업에 사용하던 인터페이스이다.


  • 하지만 State Full하다는 특징 때문에

  • 치명적인 단점이 존재하게 된다.


  • 또한 String <-> Object 변환만 가능하기 때문에

  • 자유롭지 못하다는 단점도 존재한다.

  • 단점과 관련해서는

  • PropertyEditor의 단점 부분을 참고하자.


Code를 통한 실습

Controller

@RestController
public class EventController {

    @GetMapping("/event/{event}")
    public String getEvent(@PathVariable Event event){
        System.out.println(event);
        return event.getId().toString();
    }
}

Object

public class Event {

    private Integer id;

    private String name;

    public Event(Integer id) {
        this.id = id;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

TC

@RunWith(SpringRunner.class)
@WebMvcTest
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;

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

TC Result : Fail

  • 1이라는 프로퍼티를

  • Event라는 도메인 객체로 변환할 수 없기 때문에

  • 테스트는 실패한다.

2019-09-02 09:19:14.877  WARN 27076 --- [           main] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'goodgid.study.spring.Event'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'goodgid.study.spring.Event': no matching editors or conversion strategy found]

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /event/1
       Parameters = {}
          Headers = []
             Body = <no character encoding set>
    Session Attrs = {}

...

MockHttpServletResponse:
           Status = 500
    Error message = null
          Headers = []
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

java.lang.AssertionError: Status 
Expected :200
Actual   :500

Editor

  • 사용자가 요청한 값을

  • 해당 도메인 객체로 변환할 수 있는 Editor를 생성해보자.

  • PropertyEditorSupport를 상속받아

  • 필요한 메소드만 Override한다.

public class EventEditor extends PropertyEditorSupport {

    @Override
    public String getAsText() { // Return Type인 String으로 변환한다.
        return super.getAsText();
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        setValue(new Event(Integer.parseInt(text))); // 입력받은 String을 Object로 변환한다.
    }
}
  • 그리고 다시 TC를 돌리면 성공하는 것을 확인할 수 있다.
MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /event/1
       Parameters = {}
          Headers = []
             Body = <no character encoding set>
    Session Attrs = {}

...

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"1"]
     Content type = text/plain;charset=UTF-8
             Body = 1
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

PropertyEditor의 단점

String <-> Ojbect 변환

  • 위 코드에서 보면 알 수 있듯이

  • PropertyEditor는 String <-> Object 변환만 가능하다.

  • 그렇기 때문에

  • A Object <-> B Object와 같은 변환은 불가능하다.

State Full

  • 위에서 언급했듯이

  • PropertyEditor는

  • State Full하다는 특징으로 인해

  • 치명적인 단점이 발생한다.


  • 상태 정보를 저장하고 있기 때문에

  • 싱글톤 빈으로 등록하여 사용하였을 경우에

  • 다음과 같은 상황이 발생할 수 있다.

A유저가 setValue()를 호출하였는데
PropertyEditor가
B유저에게 setValue() 할 수 있다.

A유저가 getValue()를 호출하였는데
PropertyEditor가
B유저 정보를 Return 할 수 있다.
  • Thread Safe 하지 않다.

  • 그렇기 때문에

  • 서로 다른 Thread에게 공유가 된다.

  • 그러므로 PropertyEditor의 구현체는

  • 여러 Thread에서 공유해서 사용하면 안된다.


  • 다시말해 Bean으로 등록해서 사용하면 안된다.

  • 이런 단점을 보완하고자 스프링에서는 3.0 이후부터

  • 데이터 바인딩과 관련해

  • ConverterFormater와 같은 기능들이 추가되었다.

  • 이와 관련해서는

  • Spring 프레임워크 핵심 기술 - Converter와 Formatter 1부을 참고하자.


  • 그럼에도 PropertyEditor를 사용해야한다면

  • 그냥 싱글톤 빈이 아니라

  • Thread Scope의 Bean으로 등록하여 사용해야 한다.

  • 그렇게 등록을 하면

  • 한 Thread 내에서만 유효하다.


  • 하지만 빈 등록을 하여

  • PropertyEditor를 사용하기보다는

  • 다른 방법으로 사용하는걸 추천한다.


  • @InitBinder라는 어노테이션을 사용하여

  • Editor를 Global하게 사용하는게 아니라

  • 해당 컨트롤러에서

  • 특정 Editor를 지정하여 사용하는 것이 안전한다.

@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);
        return event.getId().toString();
    }
}

Reference


Comments

Index