Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

[Spring 레퍼런스] 6장 유효성검사(validation), 데이터 바인딩, 타입 변환 #2

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.


6.5 Spring 3 타입 변환
스프링 3는 일반적인 타입변환 시스템을 위해 core.convert 패키지를 도입했다. 타입변환 시스템은 타입변환 로직을 구현하는 SPI와 런타임시에 타입변환을 실행하는 API를 정의한다. 스프링 컨테이너에서 구체적인 빈 프로퍼티값을 필요한 프로퍼티 타입으로 변환하는 프로퍼티 에디터 대신에 타입변환 시스템을 사용할 수 있다. 어플리케이션내에서 타입변환이 필요한 어디서든 퍼블릭 API를 사용할 수 있다.


6.5.1 Converter SPI
타입변환 로직을 구현하는 SPI는 간단하고 강타입(strongly typed)이다.

package org.springframework.core.convert.converter;

public interface Converter<S, T> {

  T convert(S source);

}



자신만의 Converter를 만들려면 위의 인터페이스를 구현하면 된다. 파라미터 S는 변환되기 전의 타입이고 파라미터 T는 변환할 타입이다. convert(S)를 호출할 때 source 아규먼트는 null이 아니라는 것을 보장해야 한다. 작성한 Converter는 변환에 실패했을 때 Exception을 던질 것이다. source의 값이 유효하지 않은 경우 IllegalArgumentException을 던져야 한다. 작성한 Converter 구현체가 쓰레드 세이프하도록 해야 한다.

core.convert.support 패키지에 핀리한 여러 가지 컨버터 구현체가 있다. 이 구현체들은 문자열을 숫자나 다른 일반적인 타입으로 변환하는 컨버터가 포한되어 있다. Converter 구현의 예제로 StringToInteger를 보자.

package org.springframework.core.convert.support;

final class StringToInteger implements Converter<String, Integer> {

  public Integer convert(String source) {
    return Integer.valueOf(source);
  }

}




6.5.2 ConverterFactory
String을 java.lang.Enum 객체로 변환하는 등 전체 클래스 계층에서 변환로직을 한 곳에 모으려고 한다면 ConverterFactory를 구현해라.

package org.springframework.core.convert.converter;

public interface ConverterFactory<S, R> {

  <T extends R> Converter<S, T> getConverter(Class<T> targetType);

}



파라미터 S는 변환하기 전의 타입이고 파라미터 R은 변환할 클래스의 범위를 정의하는 기본타입이다. 이제 getConverter(Class<T>)를 구현해라. T는 R의 슈퍼클래스다.

StringToEnum ConverterFactory 예제를 보자.

package org.springframework.core.convert.support;

final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {

  public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
    return new StringToEnumConverter(targetType);
  }

  private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {

    private Class<T> enumType;

    public StringToEnumConverter(Class<T> enumType) {
      this.enumType = enumType;
    }

    public T convert(String source) {
      return (T) Enum.valueOf(this.enumType, source.trim());
    }
  }
}




6.5.3 GenericConverter
세련 된 Converter 구현체가 필요하다면 GenericConverter 인터페이스를 고려해봐라. 훨씬 유연하면서도 타입 제약이 적은 GenericConverter는 여러 가지 타입의 소스와 타겟간의 변환을 지원한다. 게다가 GenericConverter는 자신만의 변환 로직을 구현할 때 소스와 타겟 필드 컨텍스트를 사용할 수 있게 해준다. 이러한 컨텍스트로 필드 어노테이션이나 필드정의에 선언된 제너릭 정보로 타입변환을 할 수 있다.

package org.springframework.core.convert.converter;

public interface GenericConverter {

  public Set<ConvertiblePair> getConvertibleTypes();

  Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

}



GenericConverter를 구현하려면 지원하는 source->target 타입의 쌍을 반환하는 getConvertibleTypes()를 정의하고 변환 로직을 위해 convert(Object, TypeDescriptor, TypeDescriptor)를 구현한다. 소스 TypeDescriptor는 변환될 값이 있는 소스 필드에 접근하게 해주고 타겟 TypeDescriptor는 변환된 값이 할당될 타겟 필드에 접근하게 해준다.

자바 배열과 컬렉션을 변환해주는 컨버터는 GenericConverter의 좋은 예제다. 이 ArrayToCollectionConverter는 컬렉션의 엘리먼트 타입을 위한 타겟 컬렉션 타입을 선언한 필드를 가진다. 이는 타겟 필드에 컬렉션을 할당하기 전에 소스 배열의 각 엘리먼트를 컬렉션 엘리먼트 타입으로 변환한다.

Note
GenericConverter은 훨씬 복잡한 SPI 인터페이스이므로 필요할때만 사용해야 한다. 기본적인 타입 변환이 필요하다면 Converter나 ConverterFactory를 사용해라.


6.5.3.1 ConditionalGenericConverter
때 로는 지정한 상태가 참일 경우에만 Converter를 실행하고 싶을 것이다. 예를 들어 타겟 필드에 어노테이션을 지정했을 때만 Converter를 실행하고 싶을 것이다. 또는 정적 valueOf 메서드처럼 지정한 메서드가 타켓클래스에 정의되었을 때만 Converter를 실행하기를 원할 것이다. GenericConverter의 하위인터페이스인 ConditionalGenericConverter는 이러한 커스텀 크리테리아 검사를 정의할 수 있다.

public interface ConditionalGenericConverter extends GenericConverter {

  boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);

}



퍼시스턴트 엔티티 식별자와 엔티티 참조간의 변환을 하는 EntityConverter가 ConditionalGenericConverter의 좋은 예이다. 이러한 EntityConverter는 타겟 엔티티 타입이 정적 finder 메서드(예: findAccount(Long))를 정의했을 때만 수행된다. matches(TypeDescriptor, TypeDescriptor)의 구현체에서 이러한 finder 메서드 검사를 수행한다.


6.5.4 ConversionService API
ConversionService는 런타임시에 타입 변환 로직을 실행하는 통일된 API를 정의한다. 때로는 이러한 퍼사드 인터페이스뒤에서 컨버터가 실행된다.

package org.springframework.core.convert;

public interface ConversionService {

  boolean canConvert(Class<?> sourceType, Class<?> targetType);

  <T> T convert(Object source, Class<T> targetType);

  boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

  Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

}



대부분의 ConversionService 구현체는 컨버터를 등록하는 SPI를 제공하는 ConverterRegistry도 구현하고 있다. 내부적으로 ConversionService 구현체는 타입변환 로직 수행을 등록된 컨버터에 위임한다.

신뢰할 수 있는 ConversionService 구현체는 core.convert.support 패키지에 있다. GenericConversionService는 대부분에 환경에서 사용할 수 있는 범용적인 구현체이다. ConversionServiceFactory는 공통적인 ConversionService 설정을 생성하는 편리한 팩토리를 제공한다.


6.5.5 ConversionService 설정
ConversionService 는 어플리케이션 구동시에 인스턴스화되고 여러 쓰레드 사이에서 공유되도록 설계된 무상태의 객체이다. 스프링 어플리케이션에서는 보통 스프링 컨테이너(또는 ApplicationContext)마다 ConversionService 인스턴스를 설정한다. 설정한 ConversionService를 스프링이 선택해서 프레임워크가 타입변환을 수행해야 할 때마다 사용할 것이다. 이 ConversionService를 어떤 빈에라도 주입해서 직접 호출할 수도 있다.

Note
스프링에 등록된 ConversionService가 없으면 원래의 PropertyEditor기반 시스템을 사용한다.


conversionService id의 다음 빈 정의를 추가해서 스프링에 기본 ConversionService를 등록한다.

<bean id="conversionService"
    class="org.springframework.context.support.ConversionServiceFactoryBean"/>



기본 ConversionService는 문자열, 숫자, 이넘(enums), 컬렉션, 맵 등의 타입을 변환한다. converters 프로퍼티를 설정해서 자신의 커스텀 컴버터로 기본 컨버터를 보완하거나 오버라이드할 수 있다. 프로퍼티 값은 Converter, ConverterFactory, GenericConverter 인터페이스를 구현할 것이다.

<bean id="conversionService"
    class="org.springframework.context.support.ConversionServiceFactoryBean">
  <property name="converters">
    <list>
      <bean class="example.MyCustomConverter"/>
    </list>
  </property>
</bean>



스프링 MVC 어플리케이션에서 ConversionService를 사용하는 것도 일반적이다. <mvc:annotation-driven/>로 사용하는 방법은 Section 6.6.5, “Spring MVC에서의 포매팅 설정”를 봐라.

변환하는 과정에서 포매팅을 적용해야 하는 경우도 있다. FormattingConversionServiceFactoryBean를 사용하는 방법은 Section 6.6.3, “FormatterRegistry SPI”를 봐라.


6.5.6 프로그래밍적인 ConversionService의 사용
다른 빈에 ConversionService 인스턴스의 참조를 주입해서 프로그래밍적으로 ConversionService 인스턴스를 사용할 수 있다.

@Service
public class MyService {

  @Autowired
  public MyService(ConversionService conversionService) {
    this.conversionService = conversionService;
  }

  public void doIt() {
    this.conversionService.convert(...)
  }
}




6.6 Spring 3 필드 포매팅
이전 섹션에서 얘기했듯이 core.convert는 범용적인 타입변환 시스템이다. 이는 한 타입에서 다른 타입으로 변환하는 로직을 구현하는 강타입의 Converter SPI처럼 통일된 ConversionService API를 제공한다. 스프링 컨테이너는 빈 프로퍼티의 값을 바인딩하는데 이 시스템을 사용한다. 게다가 스프링 표현언어 (SpEL)와 DataBinder는 둘 다 필드 값을 바인딩 하는데 이 시스템을 사용한다. 예를 들어 expression.setValue(Object bean, Object value)를 실행하기 위해 SpEL이 Short를 Long으로 강제해야 할 때 core.convert 시스템이 강제한다.

웹 어플리케이션이나 데스크톱 어플리케이션같은 전형적인 클라이언트 환경에서의 타입변환 요구사항을 생각해 보자. 이러한 환경에서는 보통 String을 클라이언트의 포스트백(postback) 과정을 지원하도록 변환하고 뷰 렌더링 과정을 위해 다시 String로 변환한다. 또한 때로는 문자열 값을 로컬라이징할 필요가 있다. 더 일반적인 core.convert Converter SPI는 포매팅같은 요구사항을 직접 다루지 않는다. 이러한 것들을 직접 다루기 위해 스프링 3는 PropertyEditor 대신 클라이언트 환경에서 사용할 수 있는 간단하고 신뢰할 수 있으며 편리한 Formatter SPI를 도입했다.

보통 범용적인 타입 변환 로직을 구현할 때 Converter SPI를 사용하는데 java.util.Date와 java.lang.Long 간에 변환을 하는 경우이다. 웹 어플리케이션같은 클라이언트 환경에서 작업하고 로컬라이징된 필드값을 파싱해서 출력해야 할 때 Formatter SPI를 사용해라. ConversionService는 두 SPI에 대한 일관된 타입변환 API를 제공한다.


6.6.1 Formatter SPI
필드 포매팅 로직을 구현하는 Formatter SPI는 간단하면서도 강타입이다.

package org.springframework.format;

public interface Formatter<T> extends Printer<T>, Parser<T> {
}



Formatter는 Printer와 Parser 인터페이스를 상속받는다.

public interface Printer<T> {
  String print(T fieldValue, Locale locale);
}




import java.text.ParseException;

public interface Parser<T> {
  T parse(String clientValue, Locale locale) throws ParseException;
}



위의 Formatter 인터페이스를 구현해서 자신만의 Formatter를 만들 수 있다. 파라미터 T는 포매팅할 객체의 타입으로 java.util.Date등이 될 수 있다. T의 인스턴스를 클라이언트 로케일(locale)로 출력하는 print()를 구현한다. 클라이언트 로케일이 반환한 포매팅된 표현에서 T의 인스턴스를 파싱하는 parse()를 구현해라. 작성한 Formatter가 파싱에 실패하면 ParseException나 IllegalArgumentException를 던져야 한다. Formatter가 쓰레드세이프하게 구현되도록 신경써라.

여러가지 포매터 구현체는 편리하게 format 하위패키지 아래 있다. number는 java.text.NumberFormat를 사용해서 java.lang.Number 객체를 포매팅하는 NumberFormatter, CurrencyFormatter, PercentFormatter를 제공한다. datetime 패키지는 java.util.Date 객체를 java.text.DateFormat로 포매팅하는 DateFormatter를 제공한다. datetime.joda 패키지는 Joda 시간 라이브러리에 기반해서 포괄적인 datetime 포매팅을 지원한다.

Formatter 구현체의 예제로 DateFormatter를 보자.

package org.springframework.format.datetime;

public final class DateFormatter implements Formatter<Date> {

  private String pattern;

  public DateFormatter(String pattern) {
    this.pattern = pattern;
  }

  public String print(Date date, Locale locale) {
    if (date == null) {
      return "";
    }
    return getDateFormat(locale).format(date);
  }

  public Date parse(String formatted, Locale locale) throws ParseException {
    if (formatted.length() == 0) {
      return null;
    }
    return getDateFormat(locale).parse(formatted);
  }

  protected DateFormat getDateFormat(Locale locale) {
    DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
    dateFormat.setLenient(false);
    return dateFormat;
  }

}



스프링 개발팀은 커뮤니티가 포매터를 추가하는 것을 환영하고 있다. 포매터에 공헌하려면 http://jira.springframework.org를 봐라.


6.6.2 어노테이션 기반의 포매팅
다음과 같이 필드 타입이나 어노테이션으로 필드 포매팅을 설정할 수 있다. 포매터에 어노테이션을 바인딩하려면 AnnotationFormatterFactory를 구현해라.

package org.springframework.format;

public interface AnnotationFormatterFactory<A extends Annotation> {

  Set<Class<?>> getFieldTypes();

  Printer<?> getPrinter(A annotation, Class<?> fieldType);

  Parser<?> getParser(A annotation, Class<?> fieldType);

}



파라미터 A는 org.springframework.format.annotation.DateTimeFormat같은 포매팅 로직과 연결될 annotationType 필드이다. getFieldTypes()는 어노테이션이 사용된 필드의 타입을 반환한다. getPrinter()는 어노테이션이 붙은 필드의 값을 출력하는 Printer를 반환한다. getParser()는 어노테이션이 붙은 필트의 clientValue를 파싱하는 Parser를 반환한다.

아래의 AnnotationFormatterFactory 구현예제는 포매터에 NumberFormat 어노테이션을 붙혔다. 이 어노테이션은 숫자형식과 패틴을 지정하게 한다.

public final class NumberFormatAnnotationFormatterFactory
      implements AnnotationFormatterFactory<NumberFormat> {

  public Set<Class<?>> getFieldTypes() {
    return new HashSet<Class<?>>(asList(new Class<?>[] {
      Short.class, Integer.class, Long.class, Float.class,
      Double.class, BigDecimal.class, BigInteger.class }));
  }

  public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
    return configureFormatterFrom(annotation, fieldType);
  }

  public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
    return configureFormatterFrom(annotation, fieldType);
  }

  private Formatter<Number> configureFormatterFrom(NumberFormat annotation,
                                                     Class<?> fieldType) {
    if (!annotation.pattern().isEmpty()) {
      return new NumberFormatter(annotation.pattern());
    } else {
      Style style = annotation.style();
      if (style == Style.PERCENT) {
        return new PercentFormatter();
      } else if (style == Style.CURRENCY) {
        return new CurrencyFormatter();
      } else {
        return new NumberFormatter();
      }
    }
  }
}



필드에 @NumberFormat 어노테이션을 붙혀서 포매팅을 실행한다.

public class MyModel {

  @NumberFormat(style=Style.CURRENCY)
  private BigDecimal decimal;

}




6.6.2.1 포맷 어노테이션 API
포맷 어노테이션 API는 org.springframework.format.annotation 패키지에 있다. java.lang.Number 필드를 포매팅하려면 @NumberFormat를 사용하고 java.util.Date, java.util.Calendar, java.util.Long, Joda Time 필드를 포매팅하려면 @DateTimeFormat를 사용해라.

아래의 예제는 java.util.Date를 ISO Date (yyyy-MM-dd)로 포매팅하려고 @DateTimeFormat를 사용한다.

public class MyModel {

  @DateTimeFormat(iso=ISO.DATE)
  private Date date;

}




6.6.3 FormatterRegistry SPI
FormatterRegistry 는 포매터와 컨버터를 등록하는 SPI다. FormattingConversionService는 대부분의 환경에 적합한 FormatterRegistry의 구현체이다. 이 구현체는 FormattingConversionServiceFactoryBean를 사용하는 스프링 빈처럼 프로그래밍적으로나 선언적으로 설정할 수 있다. 이 구현체가 ConversionService도 구현했기 때문에 스프링의 DataBinder와 스프링 표현언어(SpEL)를 사용해서 직접설정할 수도 있다.

아래의 FormatterRegistry SPI를 보자.

package org.springframework.format;

public interface FormatterRegistry extends ConverterRegistry {

  void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);

  void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);

  void addFormatterForFieldType(Formatter<?> formatter);

  void addFormatterForAnnotation(AnnotationFormatterFactory<?, ?> factory);

}



위에서 보았듯이 Formatter는 fieldType이나 어노테이션으로 등록할 수 있다.

FormatterRegistry SPI는 여러 컨트롤러에서 중복된 설정을 하는 대신에 중앙에 포매팅 규칙을 설정할 수 있다. 예를 들어 모든 Date 필드를 특정 방법으로 포매팅하거나 특정 어노테이션을 가진 필드를 특정 방법으로 포매팅하는 것을 강제할 수 있다. 공유된 FormatterRegistry로 이러한 규칙을 한번만 정의하고 포매팅이 필요한 곳마다 적용한다.


6.6.4 FormatterRegistrar SPI
FormatterRegistrar는 FormatterRegistry를 통해 포매터와 컨버터를 등록하는 SPI다.

package org.springframework.format;

public interface FormatterRegistrar {

  void registerFormatters(FormatterRegistry registry);

}



Date 포매팅처럼 주어진 포매팅 분류에 따라 관련된 여러가지 컨버터와 포매터를 등록할 때 FormatterRegistrar가 유용하다. 선언적인 등록이 충분하지 않을 때도 유용하다. 예를 들어 포매터가 포매터의 <T>와는 다른 특정 필드 타입하에 색인되어야 하거나 Printer/Parser 쌍을 등록하는 경우이다. 다음 섹션에서는 컨터버와 포매터 등록에 대해서 더 자세히 얘기한다.


6.6.5 Spring MVC에서의 포매팅 설정
스 프링 MVC 어플리케이션에서 MVC 네임스페이스의 annotation-driven 요소의 속성으로 커스텀 ConversionService 인스턴스를 명시적으로 설정할 수 있다. 컨트롤러 모델 바인딩을 하면서 타입변환이 필요할 때마다 이 ConversionService를 사용한다. 명시적으로 설정하지 않은 경우 스프링 MVC는 숫자나 날짜같은 일반적인 타입에 대한 기본 포매터와 컨버터를 자동으로 등록할 것이다.

기본 포매팅 룰을 사용하는데 스프링 MVC 설정 XML에서 어떤 커스턴 설정도 필요없다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:mvc="http://www.springframework.org/schema/mvc"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

  <mvc:annotation-driven/>

</beans>



이 한줄의 설정으로 @NumberFormat와 @DateTimeFormat 어노테이션을 포함해서 숫자와 날짜타임에 대한 기본 포매터를 설정할 수 있다. 클래스패스에 Joda Time 라이브러리가 있다면 Joda 타임 포매팅 라이브러리에 대한 완전한 지원을 할 수 있다.

커스텀 포매터와 컨버터가 등록된 ConversionService 인스턴스를 주입하려면 conversion-service 속성을 설정한 뒤 FormattingConversionServiceFactoryBean의 프로퍼티로 커스텀 컨버터나 포매터, FormatterRegistrar를 지정해라.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:mvc="http://www.springframework.org/schema/mvc"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

  <mvc:annotation-driven conversion-service="conversionService"/>

  <bean id="conversionService"
      class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
      <set>
        <bean class="org.example.MyConverter"/>
      </set>
    </property>
    <property name="formatters">
      <set>
        <bean class="org.example.MyFormatter"/>
        <bean class="org.example.MyAnnotationFormatterFactory"/>
      </set>
    </property>
    <property name="formatterRegistrars">
      <set>
        <bean class="org.example.MyFormatterRegistrar"/>
      </set>
    </property>
  </bean>

</beans>




Note
FormatterRegistrar 사용에 대한 자세한 내용은 Section 6.6.4, “FormatterRegistrar SPI” 와 FormattingConversionServiceFactoryBean를 봐라.


6.7 Spring 3 유효성 검사(Validation)
스 프링 3에서는 유효성검사에 대한 지원이 여러 모로 강화되었다. 우선 JSR-303 Bean Validation API를 이제 완전히 지원한다. 두번째로 프로그래밍적으로 사용할 때 스프링의 DataBinder는 객체에 대한 바인딩 뿐만 아니라 객체의 유효성검사도 할 수 있다. 세번째로 스프링 MVC는 @Controller 입력에 대한 유효성 검사를 선언적으로 할 수 있다.


6.7.1 JSR-303 Bean Validation API의 개요
JSR- 303는 자바플랫폼의 유효성 검사 제약사항 선언과 메타데이터를 표준화한다. JSR-303 API를 사용해서 선언적인 유효성 제약사항으로 도에인 모델 프로퍼티에 어노테이션을 붙히고 런타임시에 이를 강제할 수 있다. 사용할 만한 다수의 내장 제약사항이 존재한다. 물론 자신만의 커스텀 제약사항도 정의할 수 있다.

설명을 위해 2개의 프로퍼티를 가진 간단한 PersonForm 모델을 생각해 보자.

public class PersonForm {
  private String name;
  private int age;
}




JSR-303으로 이러한 프로퍼티에 대한 유효성 검사 제약사항을 선언적으로 정의할 수 있다.

public class PersonForm {

  @NotNull
  @Size(max=64)
  private String name;

  @Min(0)
  private int age;

}




JSR-303 Validator가 이 클래스의 인스턴스를 검사할 때 이러한 제약사항을 강제할 것이다.

JSR-303에 대한 일반적인 내용은 Bean Validation Specification를 봐라. 기본 레퍼런스 구현체의 특정 능력에 대한 내용은 Hibernate Validator 문서를 봐라. JSR-303 구현체를 어떻게 스프링 빈으로 설정하는지 알고 싶으면 계속 읽어봐라.


6.7.2 Bean Validation 구현체 설정
스 프링은 JSR-303 Bean Validation API를 완전히 지원한다. 이는 JSR-303 구현체를 스프링 빈으로 편리하게 설정하도록 하는 것도 포함한다. 또한 어플리케이션에서 유효성검사가 필요할 때마다 javax.validation.ValidatorFactory나 javax.validation.Validator를 주입할 수 있다.

기본 JSR-303 Validator를 스프링 빈으로 설정하려면 LocalValidatorFactoryBean를 사용해라.

<bean id="validator"
    class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>




위의 기본 설정은 JSR-303의 기본 부트스트랩 메카니즘을 사용해서 JSR-303을 초기화한다. Hibernate Validator같은 JSR-303 프로바이더는 클래스패스에 존재해야 하고 자동적으로 탐지될 것이다.


6.7.2.1 Validator 주입
LocalValidatorFactoryBean 는 org.springframework.validation.Validator뿐만 아니라 javax.validation.ValidatorFactory와 javax.validation.Validator를 모두 구현한다. 이러한 인터페이스에 대한 참조를 유효성검사 로직을 실행해야하는 빈에 주입할 것이다.

JSR-303 API를 직접 사용하는 걸 좋아한다면 javax.validation.Validator에 대한 참조를 주입해라.

import javax.validation.Validator;

@Service
public class MyService {

  @Autowired
  private Validator validator;



빈(bean)이 Spring Validation API를 필요로 한다면 org.springframework.validation.Validator에 대한 참조를 주입해라.

import org.springframework.validation.Validator;

@Service
public class MyService {

  @Autowired
  private Validator validator;

}




6.7.2.2 커스텀 제약사항(Constraints) 설정
각 JSR-303 유효성검사 제약사항은 두 부분으로 구성되어 있다. 첫째, 제약사항과 설정가능한 제약사항의 프로퍼티를 설정하는 @Constraint. 두번째는 제약사항의 동작을 구현하는 javax.validation.ConstraintValidator 인터페이스의 구현체이다. 선언과 구현체의 연결을 위해 각 @Constraint 어노테이션은 대응되는 ValidationConstraint 구현체를 참조한다. 런타임시에 ConstraintValidatorFactory는 제약사항 어노테이션이 도메인 모델을 만났을 때 참조된 구현체를 인스턴스화한다.

기본적으로 LocalValidatorFactoryBean는 스프링이 ConstraintValidator 인스턴스를 생성하려고 사용하는 SpringConstraintValidatorFactory를 설정한다. 이는 다른 스프링 빈처럼 의존성 주입의 이점을 가진 커스텀 ConstraintValidator를 사용할 수 있다.

다음은 의존성주입을 위해 스프링을 사용하는 관련 ConstraintValidator 구현체가는 붙은 커스텀 @Constraint 선언의 예제이다.

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}





import javax.validation.ConstraintValidator;

public class MyConstraintValidator implements ConstraintValidator {

  @Autowired;
  private Foo aDependency;

  ...
}




여기서 보듯이 ConstraintValidator 구현체는 다른 스프링 빈처럼 @Autowired로 의존성을 가진다.


6.7.2.3 추가적인 설정 옵션
대 부분의 경우에 기본 LocalValidatorFactoryBean 설정으로도 충분하다. 메시지 삽입부터 탐색 처리(traversal resolution)까지 다양한 JSR-303 생성에 대한 다수의 설정 옵션이 있다. 이러한 옵션에 대한 자세한 내용은 LocalValidatorFactoryBean의 JavaDoc을 봐라.


6.7.3 DataBinder 설정
스 프링 3부터 DataBinder 인스턴스는 Validator와 함께 설정할 수 있다. 일단 설정되면 binder.validate() 호출에 의해서 Validator가 실행된다. 유효성 검사 오류는 자동적으로 바인더의 BindingResult에 추가된다.

프로그래밍적으로 DataBinder를 사용하는 경우 타겟 객체에 바인딩한 후 유효성 검사 로직을 실행하려고 DataBinder를 사용한다.

Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());

// 타겟 객체에 바인딩
binder.bind(propertyValues);

// 타겟객체의 유효성 검사
binder.validate();

// 유효성검사 오류를 포함한 BindingResult 획득
BindingResult results = binder.getBindingResult();





6.7.4 Spring MVC 3 Validation
스프링 3부터 스프링 MVC는 @Controller의 입력을 자동으로 유효성감사할 수 있다. 이전 버전에서는 개발자가 수동으로 유효성검사 로직을 실행해야 했다.


6.7.4.1 @Controller 입력의 유효성 검사 실행
입력 아규먼트에 간단히 @Valid 어노테이션을 붙혀서 @Controller 입력에 대한 유효성검사를 실행한다.

@Controller
public class MyController {

  @RequestMapping("/foo", method=RequestMethod.POST)
  public void processFoo(@Valid Foo foo) { /* ... */ }




적절한 Validator가 설정되었다면 Spring MVC 바인딩한 후 @Valid 객체의 유효성을 검사한다.

Note
@Valid 어노테이션은 표준 JSR-303 Bean Validation API의 일부이고 스프링에 한정된 것은 아니다.


6.7.4.2 Spring MVC가 사용하는 Validator 설정
@Valid 메서드 아규먼트가 있을때 호출되는 Validator 인스턴스는 2가지 방법으로 설정할 수 있다. 첫번째 방법은 @Controller의 @InitBinder 콜백내에서 binder.setValidator(Validator)를 호출하는 것이다. 이 방법으로 @Controller마다 Validator 인스턴스를 설정할 수 있다.


@Controller
public class MyController {

  @InitBinder
  protected void initBinder(WebDataBinder binder) {
   binder.setValidator(new FooValidator());
  }

  @RequestMapping("/foo", method=RequestMethod.POST)
  public void processFoo(@Valid Foo foo) { ... }

}




두번째 방법은 전역 WebBindingInitializer에서 setValidator(Validator)를 호출하는 것이다. 이 방법으로 모든 @Controllers에 걸쳐서 Validator 인스턴스를 설정할 수 있다. 이는 Spring MVC 네임스페이스로 쉽게 설정할 수 있다.


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:mvc="http://www.springframework.org/schema/mvc"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
   http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/mvc
   http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

  <mvc:annotation-driven validator="globalValidator"/>

</beans>





6.7.4.3 Spring MVC가 사용하는 JSR-303 Validator 설정
JSR-303에서 단일 javax.validation.Validator 인스턴스는 보통 유효성검사 제약사항을 선언한 모든 모델 객체의 유효성을 검사한다. 스프링 MVC에 JSR-303에 기반한 Validator를 설정하려면 Hibernate Validator같은 JSR-303 Provider를 클래스패스에 추가한다. 스프링 MVC는 자동으로 이 프로바이더를 탐지해서 모든 컨트롤러에 걸쳐서 JSR-303 지원을 활성화할 것이다.

JSR-303 지원을 활성화하는데 필요한 스프링 MVC 설정은 다음과 같다.


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:mvc="http://www.springframework.org/schema/mvc"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
   http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/mvc
   http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

  <!-- JSR-303 support will be detected on classpath and enabled automatically -->
  <mvc:annotation-driven/>

</beans>




이러한 최소의 설정으로 @Valid @Controller 입력을 만났을 때마다 JSR-303 프로바이더가 유효성을 검사한다. 다음으로 JSR-303은 입력에 대해 설정된 제약사항을 강제할 것이다. 모든 ConstraintViolation은 표준 스프링 MVC 폼태그로 랜더링할 수 있는 오류로 BindingResult에 노출된다.
2012/08/20 00:42 2012/08/20 00:42