본문 바로가기
Spring/Spring Core

[Section 3] 데이터 바인딩 추상화 : PropertyEditor

by kevinntech 2020. 10. 1.

해당 게시물은 백기선님의 스프링 프레임워크 핵심 기술 강좌를 정리한 내용 입니다.

 

 

데이터 바인딩 정의 

 

- 기술적인 관점 : 프로퍼티의 값을 타겟 객체에 설정하는 기능이다.

- 사용자 관점 : 사용자가 입력한 값을 애플리케이션 도메인 객체에 동적으로 변환해 넣어주는 기능이다.


   → 사용자가 입력한 값은 주로 문자열이며 이 문자열을 객체가 가지고 있는 다양한 프로퍼티 타입으로 변환해서

         넣어주는 기능을 말한다. 

          * 다양한 프로퍼티 타입 : int, long, boolean, Date 등 심지어 Event, Book과 같은 도메인 타입을 말한다. 

Spring은 데이터 바인딩 기능을 여러 인터페이스(PropertyEditor, Converter, Formatter)로 추상화하여 제공 합니다. 

PropertyEditor는 DataBinder 인터페이스를 통해 사용되며 Converter와 Formatter는 ConversionService를 통해 사용됩니다.

 

 

데이터 바인딩 예제 만들기

 

1) 도메인 클래스 작성

 

     다음과 같은 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 + '\'' +
                '}';
    }
}

 

2) 컨트롤러 클래스 작성

 

@RestController // Spring Mvc 관련 내용은 다른 강좌에서 진행
public class EventController {

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

}

 

     @GetMapping("[경로]/[변수명]") : @GetMapping 애노테이션에 경로를 지정할 때 {변수명} 형식을 사용할 수 있다. 

     @PathVariable : 경로에서 사용된 변수의 값을 파라미터로 받을 수 있다.

 

 

3) 테스트 클래스 작성 

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

    @Autowired
    MockMvc mockMvc;

    @Test
    public void getTest() throws Exception {
        mockMvc.perform(get("/event/1"))            // "/event/1"로 get 요청을 하면
                .andExpect(status().isOk())         // 상태가 200 OK인 것을 바라며
                .andExpect(content().string("1"));  // 결과가 1이 되길 바란다.
    }
}

 

     여기까지의 코드를 작성하고 테스트 클래스를 실행하면 아래와 같이 문자열을 Event 타입으로 변환할 수 없다는 에러가 나타난다.  

 

     해당 에러를 해결 하기 위해서 아래 내용을 참고하자.

 

 

PropertyEditor  [고전적인 방식]

 

- PropertyEditor는 스프링 3.0 이전까지 DataBinder가 변환 작업에 사용한 인터페이스 입니다.

 

- 상태 정보을 저장 하고 있어서 쓰레드-세이프 하지 않음

 

- 일반적인 싱글톤 scope 빈으로 등록해서 사용할 수 없음

 

- Object와 String 간의 변환만 할 수 있어 사용 범위가 제한적임

 

PropertyEditor를 이용한 데이터 바인딩 

 

1) PropertyEditor 구현 
  
     앞서 발생한 에러를 해결하기 위해 PropertyEditor를 구현한다. 

     PropertyEditor 인터페이스를 직접 구현해도 되지만 구현 해야 하는 메서드가 너무 많기 때문에 


     PropertyEditorSupport 클래스를 상속 받아서 필요한 메서드만 선택해서 구현할 수 있다. 

     보통 두 개의 메서드 getAsText(), setAsText()를 구현 하면 된다. 

 

public class EventEditor extends PropertyEditorSupport {
    @Override
    public String getAsText() {
        Event event = (Event) getValue();
        return super.getAsText();
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        setValue(new Event(Integer.parseInt(text)));
    }
}

 

      getAsText() : 객체(Object)를 문자열(String)으로 변환한다.

      setAsText() : 문자열(String)을 객체(Object)로 변환한다. 

 

"PropertyEditor의 구현체들은 빈으로 등록해서 사용하면 안된다. "

PropertyEditor의 getValue(), setValue() 메서드가 공유하고 있는 값은 PropertyEditor가 가지고 있는 값이며 
이 값은 서로 다른 thread에게 공유되기 때문에 쓰레드-세이프 하지 않기 때문이다.
예를 들어, 1번 회원이 5번 회원의 정보를 변경하는 치명적인 문제가 발생 할 수 있다. 
하나의 쓰레드 내에서만 유효한 thread scope의 빈으로 등록해서 사용할 수 있지만 빈으로 등록하지 않는게 좋다. 

 

2) 컨트롤러에 등록 

 

     빈(Bean)으로 등록하지 못하는 PropertyEditor를 사용하는 방법은 @InitBinder를 사용하는 것이다.

     @InitBinder 애노테이션으로 컨트롤러에서 사용할 바인더를 등록할 수 있다.  

 

@RestController
public class EventController {

    @InitBinder
    public void init(WebDataBinder webDataBinder){ // WebDataBinder는 DataBinder의 구현체 중 하나이다.
        // webDataBinder에 Event 클래스 타입을 처리할 PropertyEditor를 등록 합니다.
        webDataBinder.registerCustomEditor(Event.class, new EventEditor());
    }

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

 

 

     컨트롤러는 어떤 요청을 처리하기 전에 컨트롤러에서 등록한 데이터 바인더(webDataBinder)에  들어 있는 PropertyEditor를  
     사용 한다.  그래서 문자열로 들어온 1을 숫자로 변환한 뒤 Event 객체로 변환한다.

 

     @InitBinder : 특정 컨트롤러에서 바인딩 또는 검증 설정을 변경하고 싶을 때 사용 한다. 

 

 3) 테스트 결과 

     테스트가 성공적으로 처리된 것을 확인 할 수 있다. 

 

 

 

댓글