컨트롤러 메서드에 @ModelAttribute가 지정된 파라미터를 @Controller메서드에 추가하면 크게 3가지 작업이 자동으로 진행된다. 

1. 파라미터 타입의 오브젝트를 만든다. @ModelAttribute User user라는 파라미터 선언이 있다면 User타입의 오브젝트를 생성한다. 이를 위해 디폴트 생성자가 반드시 필요하다. @SessionAttributes에 의해 세선에 저장된 모델 오브젝트가 있다면, 새로운 오브젝트 생성하는 대신 세션에 오브젝트를 가져온다.

 

2. 준비된 모델 오브젝트의 프로퍼티에 웹 파라미터를 바인딩해주는 것이다. HTTP를 통해 전달되는 파라미터는 기본적으로 문자열로 되어 있다. 만약 모델 오브젝트의 프로퍼티가 스트링 타입이 아니라면 적절한 변환이 필요하다. 스프링에 준비되어 있는 기본 프로퍼티 에디터를 이용해서 HTTP 파라미터 값을 모델의 프로퍼티 타입에 맞게 전환한다. 전환이 불가능한 경우라면, BindingResult오브젝트 안에 바인딩 오류를 저장해서 컨트롤러로 넘겨주거나 예외를 발생시킨다. 

 

3. 모델의 값을 검증하는 것이다. 바인딩 단계에서 타입에 대한 검증은 끝났지만, 그 외의 검증할 내용이 있다면 적절한 검증기를 등록해서 모델의 내용을 검증할 수 있다. 예를 들어 필수 프로퍼티인데 값이 없다거나, 숫자의 경우 지정된 값의 범위를넘었다거나, 문자열 포맷이 틀린경우, 또는 비즈니스 로직으로 볼때 사용할 수 없는 값을 가졌을경우...등등

스프링에서는 컨트롤러 로직과 검증 로직을 분리할 수 있다. 데이터 검증은 대개 폼의 값이 바인딩되는 모델 오브젝트를 기준으로 만들 수 있기 때문에 모델에 대한 검증 코드를 분리하기 쉽다. 

 

 스프링에서 바인딩이라고 말할 때는 오브젝트의 프로퍼티에 값을 넣는 것을 말함. 반대로 프로퍼티로부터 값을 읽어오는 경우도 있다. 스프링에서는 크게 두 가지의 프로퍼티 바인딩을 지원한다. 

 1. XML설정파일을 사용해서 빈을 정의하면서 <property>태그로 값을 주입하도록 설정하는 것.

 

 2.HTTP를 통해 전달되는 클라이언트의 요청을 모델 오브젝트 등으로 변환할 경우 바인딩이 필요. 

HTTP를 통해 전달되는 정보도 기본적으로 모두 텍스트 정보. 헤더 ,URL,경로,쿠키,요청 파라미터 모두 문자열로 전달되는 값. @MVC컨트롤러에서 이런 정보를 메서드 파라미터로 전달받을 때 항상 스트링 타입만 사용X. 따라서 이 경우에도 바인딩 과정 중에 적절한 변환이 필요. 

 

 프로퍼티 바인딩은 프로퍼티의 타입에 맞게 주어진 값을 적절히 변환하고, 실제 프로퍼티의 수정자 메서드를 호출해서 값을 넣는 2 가지 작업이 필요. @Controller에서의 바인딩 기능은 @ModelAttribute파리미터 오브젝트의 프로퍼티에만 적용되는 건 아님. @RequestParam 이나 @PathVariable같은 단일 파라미터에 대한 바인딩에도 적용.

 

 

HTTP요청파라미터와 같은 문자열은 스트링 타입으로 서블릿에서 가져온다.

             setAsText()                         getValue()

          ---------->          Level타입의  ----------->  

문자열  <---------- PropertyEditor  <--------- 오브젝트 

           getAsText()                      setValue()

 

-@InitBinder

프로퍼티 에디터를 통해 문자열과 오브젝트 사이의 타입 변환이 어떻게 일어나는지 확인했음.

@MVC에는 스프링 컨테이너에 정의된 디폴트 프로퍼티 에디터만 등록되어 있음. 여기에 LevelPropertyEditor를 추가해서 Level타입의 변환이 필요할 때 사용되도록 만들어야 한다.

 먼저 컨트롤러 메서드에서 바인딩이 어떻게 일어날까? @Controller 메서드를 호출해줄 책임이 있는 AnnotationMethodHandlerAdapter는 @RequestParam이나 @ModelAttribute,@PathVariable 등처럼 HTTP요청을 파라미터 변수에 바인딩해주는 작업이 필요한 어노테이션을 만나면 WebDataBinder를 만든다. 

 

 WebDataBinder는 여러 가지 기능을 제공하는데, 그중에 HTTP요청으로부터 가져온 문자열을 파라미터 타입의 오브젝트로 변환하는 기능도 포함. 물론 이 변환 작업은 프로퍼티 에디터를 이용. 따라서 개발자가 직접 만든 커스텀 프로퍼티 에디터를 @RequestParam과 같은 메서드 파라미터 바인딩에 적용하려면 바로 이 WebDataBinder에 프로퍼티 에디터를 직접 등록해줘야 한다. 

 문제는 WebDataBinder는 외부로 직접 노출X. 어떻게 이 WebDataBinder에 프로퍼티 에디터를 등록할까? 이때는 스프링이 특별히 제공하는 WebDataBinder 초기화 메서드를 이용해야함. 

 

1
2
3
4
@InitBinder
public void initBinder(WebDataBinder dataBinder){
    dataBinder.registerCustomEditor(Level.classnew LelvelPropertyEditor());
}
cs

@InitBinder가 붙은 initBinder() 메서드는 메서드 파라미터를 바인딩하기 전에 자동으로 호출된다.  그래서 스프링의 디폴트 프로퍼티 에디터만 갖고 있는 WebDataBinder에 커스텀 프로퍼티 에디터를 추가할 수 있는 기회 제공함.

 

프로퍼티 에디터의 단점: 매번 바인딩을 할 때마다 새로운 오브젝트를 만들어야함. 싱글톤 서비스 오브젝트 중심의 스프링과 어울리지 않음.

 

Converter

문자열과 오브젝트 사이의 양방향 변환 기능을 제공하는 PropertyEditor와 다르게 Converter메서드는 소스 타입에서 타깃 타입으로의 단방향 변환만 지원한다. 물론 소스와 타깃을 바꿔서 컨버터를 하나 더 만들면 양방향 변환이 가능. Converter는 소스와 타깃의 타입을 임의로 지정할 수 있다. PropertyEditor처럼 한쪽이 스트링 타입의 문자열로 고정되어 있지 않다. 따라서 범용적으로 사용할 수 있는 컨버터를 정의할 수 있다.

 

 Converter 인터페이스는 다음과 같이 정의되어 있다. 제네릭스를 이용해 소스 타입과 타깃 타입을 미리 지정해둘 수 있다. 따라서 컨버터 API를 사용할 때 지저분한 타입 캐스팅 코드를 사용하지 않아도 된다. 

 

 public interface Converter(S,T) {

    T conver(S source);

 }

 

convert() 메서드는 매우 단순하다. 소스 타입의 오브젝트를 받아서 타깃 타입으로 변환해주면 된다. 

 

ConversionService

컨트롤러의 바인딩 작업에도 이렇게 만든 컨버터를 적용할 수 있을까? 물론 가능. 하지만 PropertyEditor처럼 Converter타입의 컨버터를 개별적으로 추가하는 대신 ConversionService 타입의 오브젝트를 통해 WebDataBinder에 설정해줘야 한다. ConversionService는 여러 종류의 컨버터를 이용해서 하나 이상의 타입 변환 서비스를 제공해주는 오브젝트를 만들 때 사용하는 인터페이스다. 보통 ConversionService를 구현한 GenericConversionService클래스를 빈으로 등록해서 사용하면 된다. 

Converter와 같은 새로운 타입 변환 오브젝트는 모두 멀티스레드에서 동시 접근이 허용되는 안정성이 보장되므로 프로퍼티 에디터처럼 매번 오브젝트를 만들 필요가 없다. 따라서 다양한 타입 변환 오브젝트를 갖고 있는 ConversionService오브젝트를 하나 정해두고 이를 모든 바인더에게 적용한다고 해도 성능 면에서 부담이 없다. 

 

 

Formatter와 FormatttingConversionService

Converter, GenericConverter, ConverterFactory 라는 세 가지의 새로운 타입 변환 API는 모두 범용적인 타입 변환을 목적으로 설계됐다. 따라서 프로퍼티 에디터처럼 문자열과 오브젝트 사이의 상호 변환이라는 특화된 변환 작업보다 유연하다.

 Fommatter는 스트링타입의 폼필드정보와 컨트롤러 메서드의 파라미터 사이에 양방향으로 적용할 수 있도록 두 개의 변환 메서드를 갖고 있다. Fomatter는 그 자체로 Converter와 같이 스프링이 기본적으로 지원하는 범용적인 타입 변환 API가 아니다. 따라서 Formatter를 구현해서 만든 타입 변환오브젝트를 GenericConversionService 등에 직접 등록은 어렵다. 대신 Formatter 구현 오브젝트를 GenericConverter타입으로 포장해서 등록해주는 기능을 가진 FormattingConversionService를 통해서만 적용될 수 있다.

 

 Formatter이너테피으스는 오브젝트를 문자열로 변환해주는 print() 메서드와 문자열을 오브젝트로 변환해주는 parse() 메서드, 두개로 구성되어 있다. 지금 까지는 주로 문자열로 들어오는 HTTP요청 파라미터를 파라미터나 모델의 프로퍼티 타입으로 변환해주는 기능을 살펴 봤는데, 이와 반대로 모델 프로퍼티를 문자열로 변환해주는 기능도 자주 사용된다. 

뷰에서 모델 오브젝트의 내용을 HTML로 만들어졸때 바로 오브젝트-문자열 타입 변환 기능이 사용된다. 그런데 다국어 서비스를 위해 지역화 메시지를 지원하는 웹 앱이라면, 같은 모델 프로퍼티 값이라도 지역설정에 따른 다른 내용으로 바꿔주는 기능이 있다면 좋을 것이다. 문제는 프로퍼티 에디터나 단순컨버터에는 현재 지역정보가 어떤 것으로 설정되어 있는지 간단히 알 수 있는 방법이 없다는 점. 반면 Formatter의 메서드에는 변화 메서드에 오브젝트나 문자열뿐 아니라 Locale타입의 현재 지역정보도 함께 제공된다. 

 

 Formatter인터페이스가 가진 두 개의 메서드는 다음과 같다.

 

String print(T object,Locale locale);

T parse(String text, Locale locale) throws ParseException;

 

 Formatter는 이렇게 컨트롤러 내의 바인딩과 타입 변환에 사용되도록 특화된 인터페이스다. 

 

-NumberFormat

다양한 타입의 숫자 변환을 지원하는 포맷터다. 문자열로 표현된 숫자를 java,lang,Nuber 타입의 오브젝트로 상호 변환해준다. Number의 서브클래스는 Byte,Double,Float,Integer,Long,Short,BigInteger, BigDecimal이 있다.

 

 @NumberFormat의 엘리먼트로 style과 pattern을 지정할 수 있다. sthle은 Style이늄의 NUMBER,CURRENCY,PERCENT 세 가지를 설정할 수 있는데, 모두 현재 지역정보를 기준으로 해서 그에 맞는 숫자, 통화, 퍼센트 표시를 지원해준다. 다중 지역 서비스를 제공하는 앱이라면 매우 유용한 기능이다. 

 

 

+ Recent posts