springframework

o.s.w IoC 핵심기술. 데이터 바인딩 추상화: PropertyEditor

Jungsoomin :) 2020. 9. 15. 00:09

데이터 바인딩 추상화 PropertyEditor ,

 

데이터바인딩은 도메인 모델의 프로퍼티에 값을 할당해주는 기능을 말한다.

사용자의 입력값은 대부분 문자열이지만 이를 int long Boolean Date , 커스텀 도메인 등으로 변환해주는 기능을 가지는 강력한 녀석이다.

 

PropertyEditor를 사용하는 DataBinder는 스프링 Mvc에 많이 사용하는데 xml설정파일의 문자열을 빈의 타입으로 변환할때, springEL 에서도 사용되는 녀석이다.


 

일단 rest방식의 컨트롤러를 놓고 @PathVariable 어노테이션으로 경로변수 값을 할당받아 자동으로 Event객체로 맵핑되는지 확인해본다.

@RestController
public class EventController {

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

        return event.getId().toString();
    }

}

 

public class Event {

    private Integer id;
    private String title;


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

    public Integer getId() {
        return id;
    }


    @Override
    public String toString() {
        return "Event{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }
}

org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get

org.springframework.test.web.servlet.result.MockMvcResultMatchers.content

org.springframework.test.web.servlet.result.MockMvcResultMatchers.status 

스테틱 메소드 호출 명단.

@RunWith(SpringRunner.class)
@WebMvcTest
class EventControllerTest {

    @Autowired
    MockMvc mockMvc;

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

}

해당테스트 는 실패한다. 응답은 500 이며,  String 을 Event 타입으로 변환하지 못하였다는 메시지가뜬다.

 

이에 해당하는 PropertyEditor나 ConversionService 가 필요하다는 의미이다.

 

ConversionService는 WebDataBinder가 프로퍼티에대한 변환이나 값에대한 프로퍼티 변환 둘다를 위임하는 객체라는 것을 기억하고 있다.


여기서는 PropertyEditor를 사용할 것이므로, 구현객체를 만듦이 필요하다.

  1. ProperyEditor를 구현하는 것도 가능하나 재정의할 메서드가 굉장히많다.

  2. 그러므로 PropertyEditorSupport 클래스를 상속받아 사용함이 이롭다.

  3. 자주 쓰이는 것은 getAsText() , setAsText() 메서드.

  4. 프로퍼티 에디터가 가진 값이나 객체를 받아오는 것이 getAsText()

  5. 들어온 텍스트를 원하는 타입이나 값으로 변환시켜 저장시키는 것이 setAsText()

  6. 수동 구현이며, getValue()로 값을 꺼내오고 Object 리턴타입.

  7. setValue()로 값을 저장한다.

import java.beans.PropertyEditorSupport;


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(new Event(Integer.parseInt(text)));// setValue 라는 메서드에 변환할 클래스를 생성하여 변환시킴, 매개변수인 text는 들어오는 값이다.
    }
}
  • 주의할 점은 PropertyEditorSupport 상속 "객체가 가지고 있는 값" 이므로 Thread-Safe 하지 않다!

  • 그러므로 여러개의 쓰레드가 공유하면 값이 뒤섞여바뀌어 버릴 수있다.

  • 이는 즉, static pool을 공유하는 SpringBean 으로 만들어사용하면 매우매우 위험하다는 것이다.

  • 절대로 빈으로 사용하지 않음이 안전하며, DataBinder 구현객체에 등록시켜 사용하는 것이 이롭다.


컨트롤러 범위 Validation을 할때 사용했었던 @InitBinder 인데, 매개변수가 WebDataBinder 즉 DataBinder의 구현객체이다. 

  1. WebDataBinder 의 .registerCustomEditor( Type.class , PropertyEditor ) 메서드를 사용하여 컨트롤러 범위 데이터 바인더를 등록해주는 방법이있다.

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){
        webDataBinder.registerCustomEditor(Event.class,new EventEditor());
    }

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

        return event.getId().toString();
    }

}

이렇게 되면 수동으로 정의했던 바인딩이 메서드 실행 전에 처리 되어 무사히 테스트 케이스는 성공으로 끝나게 된다.

//콘솔.
Event{id=1, title='null'}

쓰레드에 안전하지 않고,

불편한 점이 많으므로, 스프링 3버전 이상부터 FormatterConverter가 추가되었다.