컨트롤러 메소드에 @ModelAttribute가 지정된 파라미터를 @Controller 메소드에 추가하면 세 가지 작업이 자동으로 진행된다.
- 파라미터 타입의 오브젝트를 만든다. @ModelAttribute User user 라는 파림터 선언이 있다면 User타입의 오브젝트를 생성한다.
- 준비된 모델 오브젝트의 프로퍼티에 웹 파라미터를 바인딩해준다. 전환이 불가능한 경우라면, BindingResult 오브젝트 안에 바인딩 오류를 저장해서 컨트롤러로 넘겨주거나 예외를 발생시킨다.
- 모델의 값을 검증한다. 타입에 대한 검증은 끝났지만, 그 외의 검증할 내용이 있다면 적절한 검증기를 등록해서 모델의 내용을 검증할 수 있다. ⇒ 스프링에서는 컨트롤러로직과 검증 로직을 분리할 수 있다. 데이터 검증은 대개 폼의 값이 바인딩되는 모델 오브젝트를 기준으로 만들 수 있기 때문에 모델에 대한 검증 코드를 분리하기 쉽다.
스프링에서 바인딩이라고 말할 때는 오브젝트의 프로퍼티에 값을 넣는 것을 말한다.
프로퍼티 바인딩은 프로퍼티의 타입에 맞게 주어진 값을 적절히 변환하고, 실제 프로퍼티의 수정자 메소드를 호출해서 값을 넣는 두 가지 작업이 필요하다.
@InitBinder
@MVC에는 스프링 컨테이너의 정의된 디폴트 프로퍼티 에디터만 등록되어 있다. 따라서 커스텀하게 타입의 변환이 필요하면 커스텀한 프로퍼티 에디터를 만들어 사용해야 한다.
컨트롤러 메소드에서 바인딩이 어떻게 일어날까
@Controller 메소드를 호출해줄 책임이 있는 AnnotationMethodHandlerAdapter는 @RequestParam이나 @ModelAttribute, @PathVariable 등 처럼 HTTP 요청을 파라미터 변수에 바인딩해주는 작업이 필요한 어노테이션을 만나면 WebDataBinder를 만든다.
⇒ 개발자가 직접 만든 커스텀 프로퍼티 에디터를 파라미터 바인딩에 적용하려면 WebDataBinder에 프로퍼티 에디터를 직접 등록해주어야 한다.
@InitBinder
public void initBinder(WebDataBinder webDataBinder){
webDataBinder.registerCustomEditor(Level.class, new LevelPropertyEditor());
}
WebBindingInitializer
@InitBinder 메소드에서 WebDataBinder에 추가한 커스텀 프로퍼티 에디터는 메소드가 있는 컨트롤러 클래스 안에서만 동작한다. 따라서 모든 컨트롤러에 적용해도 될 만큼 많은 곳에서 필요한 프로퍼티 에디터라면 WebBindingInitializer를 이용해서 등록할 수 있다.
WebBindingInitializer 인터페이스를 구현해서 작성한다.
public class MyWebBindingInitizlier implements WebBindingInitializer{
public void initBinder(WebDataBinder binder, WebRequest request){
binder.registerCustomEditor(Level.class, new LevelPropertyEditor());
}
}
MyWebBindingInitizlier 를 빈으로 등록하고, @Controller를 담당하는 어댑터 핸들러인 AnnotationMethodHandlerAdapter의 webBindingInitializer 프로퍼티에 DI 해준다.
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="webBindingInitializer">
<bean class=".....MyWebBindingInitizlier"/>
</property>
</bean>
실습
현재 Level enum이 존재하고,
@Getter
@AllArgsConstructor
public enum Level {
GOLD(3, null), SILVER(2, GOLD), BASIC(1, SILVER);
private int value;
private Level upLevel;
public int intValue(){
return value;
}
public static Level valueOf(int value){
switch(value){
case 1 : return BASIC;
case 2 : return SILVER;
case 3 : return GOLD;
default : throw new AssertionError("Unknown value : " + value);
}
}
}
우리는 /binding?Level=1
이런식으로 요청했을 때, 아래의 코드가 정상적으로 동작했으면 한다.
@GetMapping("/binding")
public void binding(@RequestParam Level level){
log.debug("level : {}", level);
}
현재는 localhost:8080/binding?Level=GOLD
이런식으로 요청해야지만 GOLD가 적절한 enum Level로 변경되어서 들어온다.
만약 localhost:8080/binding?Level=1
로 요청한다면 500에러가 리턴된다.
우리가 원하는대로 요청한 파라미터를 Level 타입으로 변환하려면 커스텀한 프로퍼티 에디터가 필요하다.
public class LevelPropertyEditor extends PropertyEditorSupport {
@Override
public String getAsText() {
return String.valueOf(((Level)this.getValue()).intValue());
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
super.setValue(Level.valueOf(Integer.parseInt(text)));
}
}
문자열(String)
<--setAsText(),getAsText()--> Level타입의 PropertyEditor
<--getValue(), setValue()--> 오브젝트(Level)
그리고 이 커스텀한 프로퍼티 에디터를 WebDataBinder에 등록시켜주면 우리가 원하는 대로 동작하는 것을 볼 수 있다.
@InitBinder
public void initBinder(WebDataBinder webDataBinder){
webDataBinder.registerCustomEditor(Level.class, new LevelPropertyEditor());
}
'프로그래밍 노트 > SPRING' 카테고리의 다른 글
[Spring] 모델 바인딩과 검증_3(Validator, BindingResult, Errors) (0) | 2020.01.06 |
---|---|
[Spring] 모델 바인딩과 검증_2(Converter) (0) | 2019.12.26 |
[Spring] @ModelAttribute, @RequestAttribute (5) | 2019.07.17 |
[Spring] junit을 활용한 스프링 테스트 (0) | 2018.09.21 |
[Spring] 팩토리빈과 팩토리메소드 (0) | 2018.09.09 |