@ModelAttribute로 지정된 모델 오브젝트의 바인딩 작업이 실패로 끝나는 경우는 두가지다.
1. 타입변환이 불가능한 경우
2. 타입변환은 성공했지만 검증기를 이용한 검사를 통과하지 못했기 때문
폼의 서브밋을 처리하는 컨트롤러 메서드에서는 반드시 검증기를 이용해 입력 값을 확인하고, 그 결과에 따라 폼을 정상 처리하고 다음 작업으로 넘어가거나 폼을 다시 띄워서 사용자가 잘못 입력한 값을 수정하도록 해줘야 함.
스프링은 이런 검증 과정에서 사용할 수 있는 Validator라는 이름의 표준 인터페이스를 제공한다. Validator를 통한 검증 과정의 결과는 BindingResult를 통해 확인 가능.
Validator
스프링의 Validator는 스프링에서 범용적으로 사용가능한 오브젝트 검증기를 정의할 수 있는 API다. @Controller로 HTTP요청을 @ModelAtrribute모델에 바인딩할 때 주로 사용된다.
Validator인터페이스는 두개의 메서드로 구성되어 있다.
supports()는 이 검증기가 검증할 수 있는 오브젝트 타입인지 확인해주는 메서드다. supports() 메서드를 통과한 경우에만 validate()가 호출된다. 바인딩이 완료된 오브젝트를 이용해 값을 검증하는 코드를 작성한다.
Validator를 이용해 검증한 결과, 아무문제가 없다면 메서드를 정상종료하면 된다. 만약 오류가 발견되면 Errors인터페이스를 통해서 특정 필드나 모델 오브젝트 전체에 대해 오류정보를 등록할 수 있다. Errors인터페이스를 통해서 등록된 오류는 최종적으로 BindingResult에 담겨 컨트롤러에 전달된다. 검증 결과를 보고 컨트롤러는 그에 맞는 처리를 하고 뷰를 선택한다. 일반적으로 폼을 처리하는 컨트롤러 메서드라면 바인딩 또는 검증 과정에서 오류가 하나라도 발견되면 다시 폼을 띄워서 에러 메시지를 보여주고 재입력을 요구한다.
가장 대표적인 검증은 값을 입력했는지 확인하는 것.
자바스크립트로도 가능하지만 자바스크립트의 검증은 서버로 자주 전송이 일어나는 것을 막아주려는 목적으로만 사용해야 한다. 자바스크립트의 검증을 통과했다고 서버의 검증 작업을 생략하면 위험하기 떄문.
스프링에서는 앞으로 설명할 네가지 방법으로 Validator를 적용할 수 있따.
- 컨트롤러 메서드 내의 코드
- @Valid를 이용한 자동검증
- 서비스 계층오브젝트에서의 검증
- 서비스계층을 활용하는 Validator
JSR-303빈 검증기능
@Notnull과 같이 필드단위로 적용 가능. 모델 오브젝트의 필드에 달린 제약조건 어노테이션을 이용해 검증을 진행할 수 있다.
BindingResult와 MessageCodeResolver
BindingReulst에는 모델의 바인딩 작업중에 나타난 타입 변환 오류정보와 검증 작업에서 발견된 검증 오류정보가 모두 저장된다. 이 오류 정보는 보통 컨트롤러에 의해 폼을 다시 띄울때 활용된다. 폼을 출력할 때 BindingReulst에 담긴 오류정보를 활용해서 에러 메시지를 생성할 수 있다. 스프링은 기본적으로 messages.properties와 같은 프로퍼티 파일에 담긴 메시지를 가져와 에러 메시지로 활용한다.
바인딩 작업중에 타입변환을 할 수 없어서 오류가 나는 경우가 있다. 이때는 검증기를 사용했을 떄처럼 직접 에러코드 지정할 수 없다. 대신 스프링이 typeMismatch라는 에러 코드를 지정해준다. 이 경우에 DefaultMessageCodeResolver가 만들어주는 4가지 메시지 키 후보를 이용해 에러 메시지를 찾음.
MessageCodeResolver는 WebDataBinder내부적으로 사용되므로 직접 이용할 일은 없지만 어떤 이름의 메시지 키를 만들어주는지 정확히 알고 있어야 에러 메시지가 담긴 프로퍼타 파일 작성이 가능.
모델의 일생
모델은 MVC아키텍쳐에서 정보를 담당하는 컴포넌트다. 브라우저와 같은 웹 클라에서 전달된 정보를 담아 비즈니스 로직에 사용되도록 전달되는 자바오브젝트이며, 비즈니스 로직에 의해 생성된 정보를 클라이언트로 보내는 동안에 유지하고 있는 오브젝트이기도 하다.
스프링 MVC 개념을 제대로 이해하는데 가장 중요한 것은 이 모델 오브젝트가 어떻게 만들어지고,다뤄지고,사용되는가 에 대한 지식이다. 또 그 과정에서 참여하는 다양한 기능을 가진 서비스 오브젝트는 어떤 것이 있으며, 이를 어떻게 확장하거나 변경할 수 있는지도 이해해야한다. 컨트롤러나 뷰는 자바 클래스나 JSP스크립트등으로 만들어져서 그 동작 결과를 쉽게 파악하고 예측가능하다. 반면 모델은 보이지 않는 곳에서 만들어지기도 하고, 코드 한 줄 만들지 않아도 모델 오브젝트에 대한 다양한 조작이 일어나기도 한다. 어노테이션을 추가하는 것만으로 세션에 저장됐다 다시 사용되기도 하고 검증이 일어나기도 한다. 간접적인 빈 설정을 통해 모델 정보의 타입을 변환 할 수 도 있다.
HTTP요청으로부터 모델 오브젝트가 준비돼서 컨트롤러 메서드에 전달하기까지의 과정에 연관되는 것을 정리.
-ModelAttribute 파라미터
컨트롤러 메서드의 모델 파라미터와 @ModelAttribute로부터 모델 이름, 모델 타입정보를 가져온다.
-@SessionAttribute세션 저장 대상 모델 이름
모델 이름과 동일한 것이 있다면 HTTP세션에 저장해둔 것이 있는지 확인. 만약 있으면 모델오브젝트 재활용.
-WebDataBinder에 등록된 프로퍼티 에디터,컨버전 서비스
WebBindingInitializer나 @InitBinder메서드를 통해서 등록된 변환 기능 오브젝트를 이용해 HTTP요청 파라미터를 모델의 프로퍼티에 맞도록 변환해서 넣어준다. 커스텀프로퍼티 에디터, 컨버전서비스, 디폴트 프로퍼티 에디터 순으로 적용된다. 타입 변환에 실패하면 BindingResult 오브젝트에 필드 에러가 등록된다. 필드마커나 필드 디폴트가 발견되면 그에 따라 지정된 필드의 값이 설정되기도 함. 빈 폼을 나타내기위한 요청에서처럼 새로 만들어진 모델 오브젝트에 바인딩할 HTTP파라미터가 없다면 이 과정은 생략된다.
-WebDataBinder에 등록된 검증기
모델 파라미터에 @Valid가 지정되어 있다면 WebBindingInitailizer나 @InitBindier메서드를 통해 등록된 검증기로 모델을 검증한다. 검증 로직을 갖고 있는 Validator 또는 JSR-303의 어노테이션 방식의 검증기를 이용한다. 검증 결과는 BindingREsult오브젝트에 등록된다. WebDataBInder에 등록된 검증기가 없거나 @Valid어노테이션이 없다면 이 과정은 생략.
-ModelAndView의 모델 맵
모델 오브젝트는 컨트롤러 메서드가 실행되기 전에 임시 모델맵에 저장된다. 이렇게 저장된 모델 오브젝트는 컨트롤러의 메서드의 실행을 마친뒤 추가로 등록된 모델 오브젝트와 함께 ModelAndView모델 맵에 담겨 DispatcherServlet으로 전달된다.
-컨트롤러 메서드와 BindingResult 파라미터
HTTP요청을 담은 모델 오브젝트가 @MA 파라미터로 전달되면서 컨트롤러 메서드가 실행된다. 메서드의 모델 파라미터 다음에 BindingREsult타입 파라미터가 있다면 바인딩과 검증 작업의 결과가 담긴 BindingResult오브젝트가 제공된다.
BindingREsult는 ModelAndView의 모델 맵에도 자동으로 추가된다.
컨트롤러 실행을 마칠때부터 뷰가 생성되기까지의 과정에서 모델과 바인딩오류정보의 흐름과 이 과정에 관여하는 기능을 나타낸다.
-ModelAndView의 모델 맵
컨트롤러 메서드의 실행을 마치고 최종적으로 DispatcherServlet이 전달받는 결과다. 메서드 안에서 직접 또는 간접적으로 생성된 @MA오브젝트가 ModelANdVIew의 모델 맵에 담겨 있다. 폼의 정보를 담은 @MA오브젝트뿐 아니라 바인딩과 검증결과를 담은 BindingResult타입 오브젝트도 모델맵에 자동 추가.
-WebDataBinder에 기본적으로 등록된 MessageCodeResolver
WebDataBinder에 등록되어 있는 MessageCodeResolver는 바인딩 작업 또는 검증 작업에서 등록된 에러코드를 확장해서 메시지 코드 후보 목록을 만들어준다. 메시지 코드 후보를 만드는 로직은 WebDataBinder에 디폴트로 등록된 것을 사용하면 된다.
-빈으로 등록된 MessageSourve와 LocaleResolver
LocaleResolver에 의해 결정된 지역정보와 MessageCodeResolver가 생성한 메시지 코드 후보 목록을 이용해 MessageSource가 뷰에 출력할 최종 에러메시지를 결정한다. MessageSouce는 기본적으로 messages.properties파일을 이용하는 ResourceBundleMessageSource를 등록해서 사용.
-@SessionAttribute 세션 저장 대상 모델 이름
모델 맵에 담긴 모델중에 @SessionAttribute로 지정한 이름과 일치하는 것이 있다면 HTTP세션에 저장된다. 세션에 저장된 모델 오브젝트는 다음 요청에서 활용된다.
-뷰의 EL과 스프링태그또는 매크로
뷰에 의해 최종 콘텐트가 생성될때 모델 맵으로 전달된 모델 오브젝트는 뷰의 표현식언어를 통해 참조되서 콘텐트에 포함된다. MessageSurce에 의해 결정된 에러메시지는 JSP라면 스프링전용태그나, 템플릿 엔진이라면 스프링 매크로를 통해 출력 가능하다.
스프링MVC를 구성하는 세가지컴포넌트 모델, 뷰 ,컨트롤러 중에서 컨트롤러와 뷰는 주로 DIspatcheerServlet의 기본 전략을 바꾸거나 재설정하므로써 결정할 수 있다. 반면 모델은 컨트롤러와 뷰보다 훨씬 다이내믹한 생명주기를 갖고 있다. 따라서 모델이 준비되고 사용되는 흐름과 그 과정에 참여하는 여러 구성요소를 잘 파악하고 이를 관리할 수 있어야 한다.