이 내용은 스프링 문서 5.2.6.RELEASE를 번역한 내용으로 오역이 있을 수 있습니다.

현재 내용 작성중입니다.

목차

핵심 기술

  1. IoC 컨테이너
  2. 리소스
  3. 검증, 데이터 바인딩, 타입 변환
    1. 스프링 Validator 인터페이스를 사용하여 검증하기
    2. 코드를 에러메세지에 연결하기
    3. BeanWrapper와 빈 관리하기
      1. 기본 프로퍼티, 중첩 프로퍼티 설정하기 그리고 획득하기
      2. 여러 내장 PropertyEditor 구현체
        1. 커스텀 PropertyEditor 구현체 추가적으로 등록하기
    4. 스프링 타입 변환
      1. Converter SPI
      2. ConverterFactory 사용하기
      3. GenericConverter 사용하기
        1. ConditionalGenericConverter 사용하기
      4. ConversionService API
      5. ConversionService 설정하기
      6. ConversionService 프로그래밍 적으로 사용하기
    5. 스프링 필드 형식지정
      1. Formatter SPI
      2. 어노테이션 기반 형식지정
        1. 형식 어노테이션
      3. FormatterRegistry SPI
      4. FormatterRegistrar SPI
      5. 스프링 MVC에 형식 설정하기
    6. 글로벌 날짜 시간 형식 설정하기
    7. 자바 빈 검증
      1. 빈 검증 개요
      2. 빈 Validator Provider 설정하기
        1. Validator 주입하기
        2. 커스텀 제한 설정하기
        3. 스프링 기반 메서드 검증
        4. 추가 설정 옵션
      3. DataBinder 설정하기
      4. 스프링 MVC 3 검증
  4. 스프링 표현 언어(SpEL)
  5. 스프링과 함께하는 관점 지향 프로그래밍(AOP)
  6. 스프링 AOP API
  7. 안전한 Null
  8. 데이터 버퍼와 코덱
  9. 부록

검증, 데이터 바인딩, 타입 변환

검증을 비즈니스 로직의 일부로 여기는 것에는 장점과 단점이 모두 있다. 스프링은 이 두가지 방식 모두를 제공하는 검증과 데이터 바인딩 방식을 가지고 있다. 특히 검증은 웹에만 한정되지 않아야하며 일부에만 적용하기 쉬워야 하고 아무 Validator나 적용할 수 있어야한다. 이러한 관점을 고려하여 스프링은 Validator를 제공한다. Validator는 어플리케이션의 모든 레이어에 간단히, 유용하게 사용할 수 있다.

데이터 바인딩은 유저 입력을 어플리케이션의 도메인 모델로 변환하는데 유용하다(또는 유저입력을 처리하고 싶은 아무 객체에나 가능하다). 스프링은 그 기능을 하는 DataBinder를 제공한다. ValidatorDataBindervalidation 패키지를 구성하며 이들은 웹 레이어에서만 국한되지 않는다.

BeanWrapper는 스프링에서 기본적인 개념이고 다양한 곳에서 사용된다. 이 챕터에서 BeanWrapper를 설명할 것이고 대부분의 경우 데이터 바인딩 목적으로 사용될 것이다. 따라서 BeanWrapper를 직접적으로 사용하지 않아도 된다. 하지만 이 문서는 레퍼런스 문서이고 순서대로 설명할 필요가 있기 에 관련 내용을 이야기할 것이다.

스프링의 DataBinder와 로우 레벨 BeanWrapperPropertyEditorSupport 구현체를 사용하여 프로퍼티 값을 파싱한다. PropertyEditorPropertyEditorSupport 타입은 자바 빈 스펙에 포함되있으며 이 챕터에서 설명할 것이다. 스프링 3에서부터 core.convert 패키지를 제공하며 이 패키지에서 일반적인 형식 변환 기능과 UI 필드 값의 형식을 정하는 하이 레벨 “format” 패키지를 제공한다. 이 패키지들을 PropertyEditorSupport 대신에 사용할 수 있다. 이 방법도 이 챕터에서 이야기할 것이다.

스프링에서는 스프링 자체 Validator와 기반 설정을 이용하여 자바 빈 검증을 제공한다. 자바 빈 검증에서 이야기한 것처럼 빈 검증을 전체적으로 활성화 할 수 있다. 또한 필요한 부분에만 사용할 수도 있다. 웹 레이어에서는 컨트롤러에만 속하는 Validator 인스턴스를 DataBinder 마다 설정해 줄수 있다. 이 방법은 DataBinder 설정하기에 설명되 있으며 커스텀 검증 로직을 추가하는데에 유용하게 사용된다.

스프링 Validator 인터페이스를 사용하여 검증하기

Validator 인터페이스는 객체를 검증하는데 사용한다. Validator인터페이스는 Errors 객체를 사용하여 검증 오류를 알려준다.

아래 데이터 객체 예시를 생각해보자

public class Person {

    private String name;
    private int age;

    // 생략된 게터와 세터
}

다음 예시는 org.springframework.validation.Validator의 아래의 두 메소드를 구현하여 Person클래스를 검증하는 예시이다:

  • supports(Class): 이 Validator가 주어진 Class를 검증할 수 있는가?
  • validate(Object, org.springframework.validation.Errors): 객체를 검증하고 오류가 확인되면 주어진 Errors 객체에 오류를 등록한다.

Validator 구현은 직관적이다. 특히 스프링이 제공하는 ValidationUtils 헬퍼 클래스를 사용한다면 더욱 직관적이다. 아래는 Person 클래스를 검증하는 Validator 구현체의 예시이다:

public class PersonValidator implements Validator {

    /**
     * 이 Validator는 Person 인스턴스만 검증한다.
     */
    public boolean supports(Class clazz) {
        return Person.class.equals(clazz);
    }

    public void validate(Object obj, Errors e) {
        ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
        Person p = (Person) obj;
        if (p.getAge() < 0) {
            e.rejectValue("age", "negativevalue");
        } else if (p.getAge() > 110) {
            e.rejectValue("age", "too.darn.old");
        }
    }
}

ValidationUtilsstatic rejectIfEmtpy(..)name프로퍼티가 null이거나 빈 문자열인 경우에 거부하기 위해 사용한다. 위의 예시 외에 ValidationUtils의 추가적인 기능은 자바독에서 볼 수 있다.

복잡한 중첩 객체를 검증하기위해 하나의 Validator 클래스를 구현하는 것도 가능하지만 각 중첩된 객체를 검증하는 Valdiator 구현체를 만들어 검증로직을 캡슐화 시키는 것이 더 좋은 방법이다. 두개의 String 프로퍼티와 Address 프로퍼티를 가지고 있는 Customer 객체의 예시를 생각해보자. AddressCustomer 객체와 별도로 사용될 수도 있을 것이다. 따라서 별개의 AddressValidator가 구현된다. CustomerValidatorAddressValidator의 로직을 사용하려면 복사-붙여넣기 대신에 의존성 주입이나 인스턴스화를 이용하여 CustomerValidatorAddressValdiator를 포함하도록 하는 것이다. 아래는 그 예시이다:

public class CustomerValidator implements Validator {

    private final Validator addressValidator;

    public CustomerValidator(Validator addressValidator) {
        if (addressValidator == null) {
            throw new IllegalArgumentException("The supplied [Validator] is " +
                "required and must not be null.");
        }
        if (!addressValidator.supports(Address.class)) {
            throw new IllegalArgumentException("The supplied [Validator] must " +
                "support the validation of [Address] instances.");
        }
        this.addressValidator = addressValidator;
    }

    /**
     * 이 Validator는 Customer 인스턴스와 자식 클래스 인스턴스를 검증한다.
     */
    public boolean supports(Class clazz) {
        return Customer.class.isAssignableFrom(clazz);
    }

    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
        Customer customer = (Customer) target;
        try {
            errors.pushNestedPath("address");
            ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
        } finally {
            errors.popNestedPath();
        }
    }
}

validator에 전달되는 Errors 객체를 통해 검증오류를 알린다. 스프링 웹 MVC의 경우 <spring:bind/> 태그를 사용하여 에러메세지를 살필수 있다. 혹은 Errors객체를 직접 확인하는 것도 가능하다. Errors객체가 제공하는 메서드에 대한 자세한 내용은 자바독에서 확인할 수 있다.

코드를 에러메세지에 연결하기

데이터 바인딩과 검증에 관한 내용을 완료했다. 이 장에서는 검증 오류에 맞는 메세지를 결과로 반환하는 방법에 대하여 이야기할 것이다. 이전 장의 예시에서 우리는 nameage필드를 검증하여 거부했었다. MessageSource를 이용하여 검증 실패 메세지를 전달하려면 필드(여기서는 name과 age)를 거부할 때 사용한 에러코드를 이용하면 된다. 우리가 Errors인터페이스의 rejectValue나 다른 reject메소드를 호출하면 내부 구현체가 전달받은 에러코드를 등록할 것이며 추가적으로 다른 에러코드도 같이 등록할 수 있다. MessageCodesResolverErrors 인터페이스가 등록한 에러코드가 어떤 것인지 결정할 것이다. 기본적으로, DefaultMessageCodesResolver는 내가 전달한 에러코드에 연결된 메세지뿐만 아니라 reject 메소드에서 거부한 필드의 이름을 포함하는 다른 메세지도 등록한다. 그래서 rejectValue("age", "too.darn.old") 메소드로 필드를 거부하는 경우, too.darn.old와는 별개로, 스프링이 too.darn.old.agetoo.darn.old.age.int도 등록한다(첫번째는 필드이름이 추가됬고 두번째는 필드의 타입도 추가됬다). 에러메세지를 노출할 때, 개발자에게 도움을 주기위해 구현되었다.

MessageCodesResolver와 기본 전략에 대하여 더 알고 싶은 경우 MessageCodesResolverDefaultMessageCodesResolver의 자바독을 살펴보면 된다.

`BeanWrapper`와 빈 관리하기

org.springframework.beans 패키지는 자바빈 표준을 충족하고 있다. 자바빈은 어규먼트가 없는 기본 생성자가 있고 네이밍 컨벤션을 따른다. 예를 들면 bingoMadness라는 프로퍼티의 세터는 setBingoMadness(..)며 게터는 getBingoMadness(..)이다. 자바빈과 그 스펙에 대하여 자세히 알고 싶으면 자바빈을 보아라.

BeansWrapper 인터페이스와 그 구현체(BeansWrapperImpl)은 beans패키지에서 중요한 클래스이다. 자바독에서 언급했듯이, BeansWrapper는 프로퍼티를 가져오고 프로퍼티를 설정하며 프로퍼티 설명자를 가져오고 프로퍼티를 질의한다. 또한 BeanWrapper는 중첩 프로퍼티도 지원한다. 깊이에 제한없이 하위 프로퍼티를 설정할 수 있다. 또한 BeanWrapper는 타겟 클래스에서 자바빈 표준을 지원하는 코드를 작성하지 않아도 자바빈 표준 PropertyChangeListenersVetoableChangeListener를 추가해 줄 수 있다. 또한 BeansWrapper는 인덱스가 설정된 프로퍼티도 지원한다. BeanWrapper는 일반적으로 어플리케이션에서 직접 사용되지 않고 DataBinderBeanFactory에서 사용된다.

BeanWrapper가 동작하는 방법은 이름에서 유추할수 있듯이 빈을 감싸서 프로퍼티를 설정하거나 가져오는 방식으로 되어있다.

기본 프로퍼티, 중첩 프로퍼티 설정하기 그리고 획득하기

BeanWrapper구현체에서 오버로드되는 메소드인 setPropertyValuegetPropertyValue를 사용하여 프로퍼티를 설정하거나 가져온다. 자바독에 자세한 내용이 있다. 아래의 표는 이러한 컨벤션의 예시이다:

표 11. 프로퍼티 예시

표현 설명
name 프로퍼티 name을 의미한다. setName(..)을 포함하며 getName()이나 isName() 메소드를 포함한다.
account.name 프로퍼티 account의 중첩 프로퍼티 name을 의미한다. getAccount().getName()이나 getAccount().setName()와 같은 메소드들을 포함한다.
account[2] 프로퍼티 account의 세번째 요소를 의미한다. 인덱스가 있는 프로퍼티는 arraylist 혹은 다른 순서가 있는 컬렉션의 형태이다.
account[COMPANYNAME] account Map 프로퍼티의 COMPANYNAME 키를 가진 엔트리의 값을 의미한다

(이 다음장은 BeanWrapper를 직접적으로 사용할 계획이 없다면 별로 중요하지 않을 수 있다. DataBinderBeanFactory, 이 두 인터페이스의 구현체만 사용할 예정이라면, PropertyEditors 섹션으로 넘어가도 좋다.)

아래의 두 예제 클래스는 BeanWrapper를 사용하여 프로퍼티를 설정하고 가져오는 예시이다:

public class Company {

    private String name;
    private Employee managingDirector;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Employee getManagingDirector() {
        return this.managingDirector;
    }

    public void setManagingDirector(Employee managingDirector) {
        this.managingDirector = managingDirector;
    }
}
public class Employee {

    private String name;

    private float salary;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public float getSalary() {
        return salary;
    }

    public void setSalary(float salary) {
        this.salary = salary;
    }
}

아래의 코드 예시는 CompaniesEmployees 인스턴스의 프로퍼티를 가져오고 변경하는 예시이다:

BeanWrapper company = new BeanWrapperImpl(new Company());
// company의 name을 설정한다.
company.setPropertyValue("name", "Some Company Inc.");
// ... 아래 방식으로도 설정할 수 있다.
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);

// director를 만들어서 company에 연결한다.
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());

// company를 통하여 managingdirector의 salary를 가져온다. 
Float salary = (Float) company.getPropertyValue("managingDirector.salary");

여러 내장 PropertyEditor 구현체

스프링은 PropertyEditor의 개념을 ObjectString간의 변환에 사용한다. 객체의 프로퍼티를 다양한 방법으로 표현할 때 유용하게 사용한다. 예를 들면, Date를 사람이 읽을 수 있는 방식으로 표현할 수 있고 (예를 들면, String:2008-02-13) 반대로 사람이 읽는 형식을 원래대로 바꿀 수 있다(더 나아가 사람이 읽을 수 있는 모든 형식을 Date객체로 바꿀 수 있다). 이 방식을 적용하려면 java.beans.PropertyEdiotr 타입의 커스텀 에디터를 등록하면 된다. BeanWrapper나 특정 역흐름제어 컨테이너에 커스텀 에디터를 등록하여 프로퍼티를 원하는 타입으로 전환하는 방법을 알려 줄 수 있다. PropertyEditor에 대한 자세한 정보는 오라클 java.beans패키지 자바독을 확인하세요.

스프링에서 프로퍼티 변환이 사용되는 예시이다:

  • PropertyEditor 구현체를 사용하여 빈에 프로퍼티를 설정한다. XML에 스프링 빈을 정의하고 String을 프로퍼티 값에 사용했을 때, 해당 프로퍼티에 대응하는 세터의 파라메터가 Class 타입이라면 ClassEditor를 사용하여 Class 객체로 변환한다.
  • 스프링 MVC 프레임워크는 PropertyEditor 구현체를 사용하여 HTTP 요청 파라메터를 변환한다.

스프링은 다양한 내장 PropertyEditor 구현체를 제공한다. org.springframework.beans.propertyeditors 패키지에 포함되어 있다. 대부분은 BeanWrapperImpl에 기본적으로 등록되어 있다. 프로퍼티 에디터는 설정가능하기 때문에 커스텀 구현체를 등록하여 기본 동작을 변경 할 수 있다. 아래 표는 스프링이 제공하는 PropertyEditor 구현체이다:

표 12. 내장 PropertyEditor 구현체

클래스 설명
ByteArrayPropertyEditor 바이트 배열을 처리하는 에디터. 문자열을 대응하는 바이트 표현으로 변환한다. BeanWrapperImpl에 의하여 기본으로 등록된다.
ClassEditor 클래스를 표현하는 문자열을 실제 클래스로 변환한다. 그 반대도 변환한다. 클래스가 없으면 IllegalArgumentException을 발생시킨다. BeanWrapperImpl에 의하여 기본으로 등록된다.
CustomBooleanEditor Boolean 프로퍼티를 변환하며 수정가능하다. BeanWrapperImpl에 의하여 기본으로 등록되지만 커스텀 에디터 인스턴스를 등록하여 덮어쓰기가 가능하다.
CustomCollectionEditor 컬렉션을 처리하는 에디터. 컬렉션을 원하는 타입의 컬렉션으로 변환한다.
CustomDateEditor java.util.Date를 처리하는 에디터. 커스텸 DateFormat을 사용할 수 있다. 기본으로 등록되지 않는다. 반드시 적절한 날짜 형식과 함께 사용자가 직접 등록해야한다.
CustomNumberEditor Number의 하위 클래스 (예를 들면, Integer, Long, Float, Double)를 처리하는 에디터. BeanWrapperImpl에 의하여 기본으로 등록되지만 커스텀 에디터 인스턴스를 등록하여 덮어쓰기가 가능하다.
FildEditor 문자열을 java.io.File 객체로 변환한다. BeanWrapperImpl에 의하여 기본으로 등록된다.
InputStreamEditor 문자열을 입뎍받아 ResourceEditorResource를 거쳐 InputStream으로 변환하는 에디터. InputStream을 닫지 않는다는 것에 유의하십시오. BeanWrapperImpl에 의하여 기본으로 등록된다.
LocaleEditor 문자열([country][variant]형식, LocaletoString()메소드와 동일)과 Locale 객체간의 변환을 한다. BeanWrapperImpl에 의하여 기본으로 등록된다.
PatternEditor 문자열과 java.util.regex.Pattern 객체간의 변환을 한다.
PropertiesEditor 문자열(java.util.Properties클래스의 자바독에 정의된 형식)과 Properties 객체간의 변환을 한다. BeanWrapperImpl에 의하여 기본으로 등록된다.
StringTrimmerEditor 문자열의 좌우 공백을 제거하는 에디터. 기본으로 등록되지 않는다. 사용자가 직접 등록해야한다.
URLEditor 문자열로 표현된 URL과 URL 객체간의 변환을 한다. BeanWrapperImpl에 의하여 기본으로 등록된다.

스프링은 java.beans.PropertyEditorManager를 사용하여 프로퍼티 에디터를 찾는 검색 경로를 설정한다. 이 검색 경로에는 sun.bean.editors가 포함되어 있고 이 경로는 Font, Color, 대부분의 기본 타입에 대응하는 PropertyEditor 구현체들을 가지고 있다. 또한 표준 자바빈즈 인프라는 PropertyEditor클래스가 처리하는 클래스와 같은 패키지에 있고 같은 이름에 Editor가 붙어 있다면 자동으로 PropertyEditor 클래스를 찾아 등록한다(명시적으로 등록할 필요가 없다). 예를 들면, 아래와 같은 패키지와 클래스가 있으면 SomethingEditor 클래스가 PropertyEditor로 등록되어 Something 타입의 처리에 사용된다.

com
  chank
    pop
      Something
      SomethingEditor // Something 클래스에 사용되는 PropertyEditor

또한 BeanInfo 자바빈즈 동작을 이용할 수 있다(여기에 자세히 설명되어 있다). 아래 예시는 BeanInfo 동작을 사용하여 PropertyEditor를 등록하는 예시이다:

com
  chank
    pop
      Something
      SomethingBeanInfo // Something 클래스를 위한 BeanInfo

아래의 자바코드는 Something 클래스의 age 프로퍼티에 CustomNumberEditor를 연결하는 SomethingBeanInfo 클래스의 코드이다4

public class SomethingBeanInfo extends SimpleBeanInfo {

    public PropertyDescriptor[] getPropertyDescriptors() {
        try {
            final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
            PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
                public PropertyEditor createPropertyEditor(Object bean) {
                    return numberPE;
                };
            };
            return new PropertyDescriptor[] { ageDescriptor };
        }
        catch (IntrospectionException ex) {
            throw new Error(ex.toString());
        }
    }
}
커스텀 PropertyEditor 구현체 추가적으로 등록하기

문자열을 프로퍼티에 사용할 때, 스프링 컨테이너는 자바빈즈 표준 PropertyEditor 구현체를 사용하여 문자열을 복잡한 자바 타입으로 변환한다. 스프링은 다양한 커스텀 PropertyEditor 구현체(예를 들면, 문자열로 표현된 클래스 이름을 Class 객체로 변환하는 에디터)를 미리 등록한다. 추가적으로, 자바의 표준 자바빈즈 ProperteyEditor 검색 동작법은 규칙에 맟게 명명되고 지원하는 대상 클래스와 같은 패키지에 있으면 자동으로 검색한다.

다른 커스텀 PropertyEditor를 등록하려면 몇가지 방법이 있다. 가장 수작업인 방법이자 추천하지 않는 방법은 BeanFactory 참조를 가지고 있을 때, ConfigurableBeanFactory인터페이스의 registerCustomEditor() 메소드를 호출하는 것이다. 또다른 (아주 약간 더 편리한) 방법은 CustomEditorConfigurer라는 빈 팩토리 후 처리기를 사용하는 것이다. BeanFactory 구현체와 빈 팩토리 후 처리기를 사용할 수 있지만, CustomEditorConfigurer는 중첩 프로퍼티 설정이 필요하다. 그래서 ApplicationDontext 사용을 추천한다. 다른 빈을 등록하는 것과 같은 방식으로 사용가능하다.

모든 빈 팩토리와 어플리케이션 컨텍스트는 BeanWrapper를 이용하여 내장 프로퍼티 에디터를 사용한다. BeanWrapper가 등록하는 표준 에디터는 이전 장에 나열되어 있다. 추가적으로 ApplilcationContext는 추가적인 에디터를 추가하거나 덮어 씌워 어플리케이션 컨텍스트 종류에 맟는 동작을 한다.

표준 자바빈즈 PropertyEditor는 문자열로 표현된 프로퍼티 값을 복잡한 프로퍼티의 타입으로 변경하는 기능을 한다. 빈 팩토리 후 처리기인 CustomEditorConfigurer를 사용하여 ApplicationContextPropertiEditor를 추가할 수 있다.

다음 예시를 보자. ExoticTypeDependsOnExoticType이 있으며 DependsOnExoticTypeExoticType을 프로퍼티로 필요로 한다.

package example;

public class ExoticType {

    private String name;

    public ExoticType(String name) {
        this.name = name;
    }
}

public class DependsOnExoticType {

    private ExoticType type;

    public void setType(ExoticType type) {
        this.type = type;
    }
}

프로퍼티를 설정할 때, 문자열을 사용하여 설정할 수 있기를 원한다. PropertyEditor가 문자열을 ExoticType 인스텬스로 변경한다. 아래의 그 설정 예시이다4

<bean id="sample" class="example.DependsOnExoticType">
    <property name="type" value="aNameForExoticType"/>
</bean>

PropertyEditor 구현체는 아래처럼 생겼다:

// converts string representation to ExoticType object
package example;

public class ExoticTypeEditor extends PropertyEditorSupport {

    public void setAsText(String text) {
        setValue(new ExoticType(text.toUpperCase()));
    }
}

마지막으로 아래 예시는 CustomEditorConfigurer를 사용하여 ApplicationContextPropertyEditor를 등록하는 예시이다4

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
        </map>
    </property>
</bean>
PropertyEditorRegistrar 사용하기

스프링 타입 변환

Converter SPI

ConverterFactory 사용하기

GenericConverter 사용하기

ConditionalGenericConverter 사용하기

ConversionService API

ConversionService 설정하기

ConversionService 프로그래밍 적으로 사용하기

스프링 필드 형식지정

Formatter SPI

어노테이션 기반 형식지정

형식 어노테이션

FormatterRegistry SPI

FormatterRegistrar SPI

스프링 MVC에 형식 설정하기

글로벌 날짜 시간 형식 설정하기

자바 빈 검증

빈 검증 개요

빈 Validator Provider 설정하기

Validator 주입하기
커스텀 제한 설정하기
스프링 기반 메서드 검증
추가 설정 옵션

DataBinder 설정하기

스프링 MVC 3 검증

다음 장에서 계속