kagamihogeの日記

kagamihogeの日記です。

Spring Framework Reference Documentation 4.1.xのIII. Core Technologies 7. Validation, Data Binding, and Type Conversionをテキトーに訳した

http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/ をテキトーに訳した。Spring Framework Reference Documentation 4.1.xのIII. Core Technologies 6. Resourcesをテキトーに訳した - kagamihogeの日記の続き。

はてなブログの最大文字数制限を超えるとエントリ内容が切り捨てられるということを初めて知る。

7. Validation, Data Binding, and Type Conversion

7.1 Introduction

JSR-303/JSR-349 Bean Validation
Spring Framework 4.0はsetup support*1の点でBean Validation 1.0 (JSR-303)およびBean Validation 1.1 (JSR-349) をサポートしており、これらはSpringのValidatorインタフェースに適用できます。
アプリケーションは、Section 7.8, “Spring Validation”で解説するように、グローバルに一度だけBean Validationを有効化するかどうかを選択可能で、すべてのvalidation要求に対して排他的になります。
また、アプリケーションは、Section 7.8.3, “Configuring a DataBinder”で解説するように、DataBinderインスタンスごとにSpringの```Validator````インタフェースを追加登録する事も可能です。これはアノテーションを使わずにvalidationのロジックをプラグインする場合に役立ちます。

ビジネスロジックとしてvalidationを捉えることには賛否両論があります。Springが提供するvalidation(とデータバインディング)の設計は、that does not exclude either one of them. *2。具体的には、validationはweb層に結合すべきではなく、ローカライズが容易で、任意のvalidatorをプラグイン形式で利用可能であるべきです。このことを考慮した結果、SpringはValidatorインタフェースという結論になり、このインタフェースは前述の事項を満たしており、アプリケーションのすべてのレイヤーで利用可能です。

データバインディングはユーザが入力を動的にアプリケーションのドメインモデル(もしくは、ユーザ入力を処理するオブジェクト)にバインドするのに有用です。Springではデータバインディングを行うためのDataBinderを提供しています。ValidatorDataBindervalidationパッケージに含まれており、主にMVCフレームワークで使われますがそれ以外でも使用可能です。

BeanWrapperSpring Frameworkの中核要素であり、様々な場所で使われています。しかし、直接BeanWrapperを使う必要はほとんど無いでしょう。その理由はこのリファレンスドキュメントにありますが、順を追っての説明が必要と考えています。このチャプターではBeanWrapperについて解説します。もしBeanWrapperを使おうとしていた場合、代わりにデータバインディングを試してみてください。

SpringのDataBinderと低レベルのBeanWrapperは両者ともプロパティ値のパースとフォーマットにPropertyEditorsを使います。PropertyEditorのコンセプトはJavaBeans仕様の一部で、これについてもこのチャプターで解説します。Spring 3では汎用的な型変換機能を持つ"core.convert"パッケージと、UIフィールド値のフォーマット用の高レベル"format"パッケージを導入しました。これらの新パッケージはPropertyEditorsをよりシンプルにしたものとして使うことが可能で、このチャプターで解説を行います。

7.2 Validation using Spring’s Validator interface

SpringのValidatorインタフェースを使うことでオブジェクトをvaliateできます。Validatorインタフェースはvalidate時にErrorsオブジェクトを使用し、validatorはErrorsオブジェクトにvalidationの結果を出力します。

以下の簡単なデータオブジェクトを例にとって考えて見ます。

public class Person {

    private String name;
    private int age;

    // the usual getters and setters...
}

org.springframework.validation.Validatorインタフェースの二つのメソッドを以下のように実装することでPersonクラスにvalidationの振る舞いを追加します。

  • supports(Class) - 引数のClassインスタンスをこのValidatorがvalidate可能かどうかを返す。
  • validate(Object, org.springframework.validation.Errors) - 引数オブジェクトをvalidateしてvalidationエラーの場合にはErrors引数にエラー結果を登録する。

以下はValidatorを適当に実装し、以下ではSpring Frameworkが提供するValidationUtilsヘルパークラスを使用しています。

public class PersonValidator implements Validator {

    /**
     * このValidatorはPersonのインスタンス*だけ*validatesする。
     */
    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 rejectIfEmpty(..)は、もし'name'プロパティがnullもしくは空文字の場合は拒否するために使います。上記のサンプル以外の機能についてはValidationUtilsjavadocを参照してください。

複雑なオブジェクトでのネスト化されたオブジェクトそれぞれをvalidateするのに単一のValidatorだけで実装可能ですが、個々のネスト化されたオブジェクトごとにvalidationロジックをカプセル化するほうが良いでしょう。複雑なオブジェクトのカンタンな例として二つのStringプロパティ(姓・名)とAddressオブジェクトを持つ例を考えます。AddressオブジェクトはCustomerオブジェクトとは独立に使うことも可能なので、個別にAddressValidatorを実装しています。コピペせずにAddressValidatorのロジックをCustomerValidatorで再利用したい場合、DIするかCustomerValidator内でAddressValidatorインスタンス可能で、以下のように使います。

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かそのサブクラスのみvalidateします。
     */
    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();
        }
    }
}

Validationエラーはvalidatorに渡されるErrorオブジェクトに追加します。Spring Web MVCの場合はエラーメッセージの表示に<spring:bind/>タグを使いますが、エラーオブジェクトを検査するコードを書くことも可能です。Errorが提供するメソッドの詳細についてはjavadocを参照してください。

7.3 Resolving codes to error messages

org.springframework.beansパッケージはOracleが定めるJavaBeans標準に準拠しています。JavaBeanはデフォルトの引数無しコンストラクタを持つ単なるクラスで、命名規約に従い、例えば、プロパティ名bingoMadnessはセッターメソッドsetBingoMadness(..)とゲッターメソッドgetBingoMadness()を持ちます。JavaBeanとその仕様に関する詳細については、Oracleのwebサイト(javabeans)を参照してください。

beansパッケージで最も重要なクラスはBeanWrapperインタフェースとそれに対応する実装(BeanWrapperImpl)です。javadocに記載されていますが、BeanWrapperは、プロパティ値の(個別あるいは一括で)setとget、プロパティ記述子の取得(get property descriptors)、読み込み可能か書き込み可能かを判定するためのプロパティへの問い合わせ、の機能を提供します。また、BeanWrapperはネスト化されたプロパティをサポートし、無限の深さでサブプロパティへプロパティを設定することが可能です。それから、BeanWrapperは標準JavaBeansのPropertyChangeListenersVetoableChangeListenersを追加可能ですが、ターゲットクラスにそのためのコードを書く必要はありません。最後に重要な点として、BeanWrapperはインデックス付きプロパティ(indexed properties)の設定機能を提供します。BeanWrapperは通常はアプリケーションコードから直接使いませんが、DataBinderBeanFactoryはその限りではありません。

BeanWrapperの動作の一部は名前で指定します。プロパティの設定と検索などは、対象ビーンのアクションを実行するのにビーンをラップします(The way the BeanWrapper works is partly indicated by its name: it wraps a bean to perform actions on that bean, like setting and retrieving properties.が原文。ちょっと訳に自信が無い)。

7.4.1 Setting and getting basic and nested properties

プロパティの設定と取得はsetPropertyValue(s)getPropertyValue(s)を使用して行われます。このメソッドにはいくつかのバリエーションがあり、詳細はjavadocに掲載されています。知っておくべきことは、オブジェクトのプロパティを指定する規約の組み合わせがいくつかある、という点です。

Table 7.1. Examples of properties

Expression Explanation
name getName()ないしisName()setName(..)メソッドに対応するプロパティ名nameを指定
account.name getAccount().setName()ないしgetAccount().getName()メソッドに対応するaccountプロパティのネスト化されたnameプロパティを指定
account[2] accountのインデックス付きプロパティの三番目の要素を指定。インデックス付きプロパティの型はarray, listもしくはその他の自然順序付き(naturally ordered)コレクションを使用可能
account[COMPANYNAME] accountMapプロパティのキーCOMPANYNAMEマッピングしたエントリの値を指定

以下にBeanWrapperでプロパティ設定と取得をする例を示します。

(以降の記述は、BeanWrapperを直接使おうとしない限り、全く不要です。DataBinderBeanFactoryとその実装を使用する場合、PropertyEditorsのセクションへとスキップしてください)

以下の二つのクラスを例にとります。

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 = BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);

// ok, let's create the director and tie it to the company:
BeanWrapper jim = BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());

// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");

7.4.2 Built-in PropertyEditor implementations

SpringはObjectString間の変換にPropertyEditorsを使います。オブジェクト自身とは異なる方法でプロパティを表現出来ると便利な場合があります。元々の日付と人間が読める形式が相互に変換して元に戻せる場合*3Dateは人間が読める形式(Stringの'2007-14-09'など)で表現可能です。この振る舞いは、java.beans.PropertyEditor型のカスタムエディタ登録(registering custom editors)によって実現可能です。Registering custom editors on a BeanWrapper or alternately in a specific IoC container as mentioned in the previous chapter, gives it the knowledge of how to convert properties to the desired type. 詳細についてはOracleが提供するjava.beansパッケージのjavadocPropertyEditorsを参照してください。

Springで使われるプロパティ編集の例は以下の通りです。

  • ビーンのプロパティ設定(setting properties on beans)はPropertyEditorsを使用して行います。XMLファイルに宣言するビーンのプロパティ値としてjava.lang.Stringで指定する場合、Springは(もし対応プロパティのsetterがClass-パラメータを持つ場合)パラメータのClassオブジェクトへと解決を試みるのにClassEditorを使います。
  • Spring MVCフレームワークのHTTPリクエストパラメータのパース(parsing HTTP request parameters)はPropertyEditorsの色々な種類を使用して行います。PropertyEditorsCommandControllerの全サブクラスに手動でバインド可能です。

Springは組み込みのPropertyEditorsを多数用意しています。その一覧のいくつかは以下に示しており、これらはすべてのorg.springframework.beans.propertyeditorsパッケージにあります。(以下に示すもので)これらのうち全てでないが大半は、デフォルトでBeanWrapperImplによって登録されます。プロパティエディタは様々な方法と場所で登録可能で、デフォルトをオーバーライドして別のものを登録可能です。

Table 7.2. Built-in PropertyEditors

Class Explanation
ByteArrayPropertyEditor バイト配列用のエディタ。Stringが対応するバイト表現に変換される。デフォルトでBeanWrapperImplによって登録される
ClassEditor String表現のクラスを実クラスにパース、またはその逆を行う。クラスが見つからない場合、IllegalArgumentExceptionがスローされる。デフォルトでBeanWrapperImplによって登録される
CustomBooleanEditor Booleanプロパティ用のカスタマイズ可能なプロパティエディタ。デフォルトでBeanWrapperImplによって登録されるが、カスタムエディタとしてのインスタンス登録でオーバーライドが可能
CustomCollectionEditor Collection用のプロパティエディタで、任意のCollectionから指定のCollection型へと変換する
CustomDateEditor java.util.Date用のカスタマイズ可能なプロパティエディタで、カスタムDateFormatをサポートする。デフォルトでは登録されない。必要に応じて適切なフォーマットでユーザが登録する
CustomNumberEditor Integer, Long, Float, DoubleなどなんらかのNumberサブクラス用のカスタマイズ可能なプロパティエディタ。デフォルトでBeanWrapperImplによって登録されるが、カスタムエディタとしてのインスタンス登録でオーバーライドが可能
FileEditor Stringをjava.ui.Fileオブジェクトへと解決する機能を持つ。デフォルトでBeanWrapperImplによって登録される
InputStreamEditor 単方向(One-way)プロパティエディタで、テキスト文字列を取りInputStreamを生成する(中間にResourceEditorResourceを経由)機能を持ち、InputStreamのプロパティはStringとして直接設定が可能。注意点として、デフォルトではInputStreamをクローズしません。デフォルトでBeanWrapperImplによって登録される
LocaleEditor StringからLocaleオブジェクトに解決またはその逆の機能を持つ(Stringフォーマットは[country][variant]で、これはLocaleのtoString()メソッドと同等)。デフォルトでBeanWrapperImplによって登録される
PatternEditor Stringをjava.util.regex.Patternオブジェクトに解決またはその逆の機能を持つ
PropertiesEditor String(java.util.Propertiesクラスのjavadocに定義されているフォーマット)をPropertiesオブジェクトに変換する機能を持つ。デフォルトでBeanWrapperImplによって登録される
StringTrimmerEditor Stringをトリムするプロパティエディタ。オプションで、空文字列をnull値に変換可能。デフォルトでは登録されない。必要に応じてユーザが登録する
URLEditor URLのString表現をURLオブジェクトの変換する機能を持つ。デフォルトでBeanWrapperImplによって登録される

必要となるプロパティエディタの検索パスを設定するのに、Springはjava.beans.PropertyEditorManagerを使います。また、FontColorと多数のプリミティブ型などのPropertyEditorを含むsun.bean.editorsが検索パスには含まれます。標準JavaBeansインフラは自動的にPropertyEditorクラスを(明示的な登録をしなくても)検索しますが、その条件は、対象クラスと同一パッケージで'Editor'を追加したクラス名にします。たとえば、以下のようなクラスとパッケージ構造のとき、Foo型のプロパティ用のPropertyEditorとして使われるのはFooEditorとなります。

com
  chank
    pop
      Foo
      FooEditor // Fooクラス用のPropertyEditor

なお、同様な方法で標準BeanInfoJavaBeansメカニズムを使うことも出来ます(in not-amazing-detail hereを参照)。以下の例では、明示的に一つ以上のPropertyEditorインスタンスを関連クラスのプロパティに登録するのにBeanInfoメカニズムを使用しています。

com
  chank
    pop
      Foo
      FooBeanInfo // Fooクラス用のBeanInfo

以下はFooBeanInfoクラスのJavaソースコードです。ここではFooクラスのageプロパティにCustomNumberEditorを関連付けています。

public class FooBeanInfo extends SimpleBeanInfo {

    public PropertyDescriptor[] getPropertyDescriptors() {
        try {
            final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
            PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Foo.class) {
                public PropertyEditor createPropertyEditor(Object bean) {
                    return numberPE;
                };
            };
            return new PropertyDescriptor[] { ageDescriptor };
        }
        catch (IntrospectionException ex) {
            throw new Error(ex.toString());
        }
    }
}
Registering additional custom PropertyEditors

文字列値としてビーンのプロパティを設定する場合、Spring IoCコンテナは最終的にはプロパティの複合型に文字列を変換するのに標準JavaBeansPropertyEditorsを使います。Springは複数のカスタムPropertyEditorsを事前に登録します(たとえば、文字列表現のクラス名から実際のClasssオブジェクトへ変換するものなど)。加えて、Javaの標準JavaBeansPropertyEditorのルックアップメカニズムは、同一パッケージで適切な名前を付けられたクラスを自動検出します。

別のカスタムPropertyEditorsを登録する必要がある場合、いくつかのメカニズムが利用可能です。ほとんどを手作業でやる方法は、大して手軽でもなく推奨もされず、ただ単にBeanFactory参照経由でConfigurableBeanFactoryインタフェースのregisterCustomEditor()を使います。または、やや手軽なものとして、CustomEditorConfigurerと呼ばわれる特別なビーンファクトリーポストプロセッサーを使うメカニズムがあります。ビーンファクトリーポストプロセッサーはBeanFactory実装と共に使いますが、CustomEditorConfigurerはネスト化されたプロパティセットアップを持つため、ApplicationContextと共に使うことが推奨され、他のビーンと同様な方法でデプロイすると、自動検出と適用が行われます。

プロパティ変換を扱うためにBeanWrapperを使いますが、すべてのビーンファクトリーとアプリケーションコンテキストは自動的に多くのビルトインプロパティエディタを使う点に注意してください。BeanWrapperが登録する標準プロパティエディタの一覧は上述のセクションを参照してください。加えて、ApplicationContextsは特定のアプリケーションコンテキスト型に適切な方法でリソースルックアップを処理するのに複数のエディタを追加ないしオーバーライドします。

標準JavaBeansPropertyEditorインスタンスは文字列表現のプロパティ値を実際の複合型のプロパティへ変換するのに使います。CustomEditorConfigurerは、ビーンファクトリーポストプロセッサーで、ApplicationContextPropertyEditorインスタンス用の便利な追加サポートを加えるのに使われます。

いま、ユーザがExoticTypeとプロパティとしてExoticTypeを設定する別のクラスDependsOnExoticTypeを作成したとします。

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;
    }
}

プロパティセットアップ時、文字列としてtypeプロパティを割り当てたい場合、裏側でPropertyEditorExoticTypeインスタンスへの変換を行います。

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

PropertyEditor実装は以下のようになります。

// 文字列表現をExoticTypeオブジェクトに変換
package example;

public class ExoticTypeEditor extends PropertyEditorSupport {

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

最後に、ApplicationContextに上記のPropertyEditorを登録するのにCustomEditorConfigurerを使うと、必要な変換が行われるようになります。

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
        </map>
    </property>
</bean>
Using PropertyEditorRegistrars

Springコンテナにプロパティエディタを登録する別のメカニズムとしてPropertyEditorRegistrarを生成して使う方法があります。このインタフェースは複数の異なる状況でプロパティエディタの同じ組み合わせを使いたい場合に特に役立ちます。PropertyEditorRegistrarsPropertyEditorRegistryというインタフェースと組み合わせて動作し、このインタフェースはSpringのBeanWrapperが実装します。PropertyEditorRegistrarsCustomEditorConfigurer(解説はコチラ)と組み合わせる場合に特に便利で、setPropertyEditorRegistrars(..)を公開しています。この方法でCustomEditorConfigurerに追加したPropertyEditorRegistrarsDataBinderとSpring MVC Controllersで簡単に共有可能です。更に、カスタムエディタの同期化の必要性を回避します。PropertyEditorRegistrarは各ビーンの生成ごとに新規のPropertyEditorインスタンス生成を要求されます。

PropertyEditorRegistrarを使用する例を示します。最初に、PropertyEditorRegistrar実装を用意して生成する必要があります。

package com.foo.editors.spring;

public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

    public void registerCustomEditors(PropertyEditorRegistry registry) {

        // 新規のPropertyEditorインスタンス生成が必要
        registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());

        // 必要なカスタムプロパティエディタを登録する...
    }
}

PropertyEditorRegistrarの実装例はorg.springframework.beans.support.ResourceEditorRegistrarも参照してください。registerCustomEditors(..)メソッドのこの実装で各プロパティエディタごとに新規インスタンスを生成していることに注意してくださ。

次に、CustomEditorConfigurerを設定してCustomPropertyEditorRegistrarインスタンスにインジェクトします。

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="propertyEditorRegistrars">
        <list>
            <ref bean="customPropertyEditorRegistrar"/>
        </list>
    </property>
</bean>

<bean id="customPropertyEditorRegistrar"
    class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>

最後に、このチャプターの主眼からは少々外れますが、Spring’s MVC web frameworkを使用する場合、データバインディングControllersSimpleFormControllerなど)とPropertyEditorRegistrarsを組み合わせて使うのは非常に簡単です。以下の例ではinitBinder(..)メソッドPropertyEditorRegistrarを使用しています。

public final class RegisterUserController extends SimpleFormController {

    private final PropertyEditorRegistrar customPropertyEditorRegistrar;

    public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
        this.customPropertyEditorRegistrar = propertyEditorRegistrar;
    }

    protected void initBinder(HttpServletRequest request,
            ServletRequestDataBinder binder) throws Exception {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder);
    }

    // other methods to do with registering a User
}

このスタイルでのPropertyEditor登録は簡潔なコード(initBinder(..)の実装はたかだか一行)であり、共通なPropertyEditor登録コードはクラスにカプセル化し、必要に応じて複数Controllersで共有可能です。

7.5 Spring Type Conversion

Spring 3では、汎用的な型変換システム(general type conversion system)のcore.converパッケージを導入しました。このシステムでは型変換ロジックを実装するためのSPIおよび実行時に型変換を実行するためのAPIを定義しています。Springコンテナ内では、このシステムは外部ビーンプロパティ値*4をプロパティの型に変換するのをPropertyEditorsの代わりに使います。また、public APIは変換が必要なアプリケーションコードの任意の場所で使用可能です。

7.5.1 Converter SPI

型変換ロジックを実装するためのSPIはシンプルかつ強く型付けされています。

package org.springframework.core.convert.converter;

public interface Converter<S, T> {

    T convert(S source);

}

自前のコンバーターを作成するには、単に上記のインタフェースを実装するだけです。パラメータ化されたS型が変換元で、T型が変換先です。こうしたコンバーターは、もしSのコレクションや配列がTのコレクションや配列に変換する必要がある場合、配列/コレクションのコンバーターをデリゲートを設定することで(デフォルトはDefaultConversionService)、透過的に適用可能です。

convert(S)の呼出しでは、ソースとなる引数はnullでないことが保証されます。自前のコンバーターでは変換が失敗する場合に非検査例外をスロー可能です。具体的には、不正なソースの場合にはIllegalArgumentExceptionをスローすべきです。自前のConverter実装はスレッドセーフなことを保証してください。

開発を容易にするため、いくつかのコンバータ実装をcore.convert.supportで提供しています。これらのコンバーターにはStringからNumberや他の頻繁に使う型への変換が含まれます。一般的なConverter実装の例としてStringToIntegerを考えます。

package org.springframework.core.convert.support;

final class StringToInteger implements Converter<String, Integer> {

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

}

7.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はコンバート可能なクラスの範囲(rangeを定義するベース型です。getConverter(Class)の実装する際、TはRのサブクラスとなります。

StringToEnumConverterFactoryの例は以下の通りです。

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());
        }
    }
}

7.5.3 GenericConverter

より洗練されたConvertの実装をしたい場合、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を実装するには、getConvertibleTypes()の戻り値にサポートするソース→ターゲット型のペアを取ります。それから、自前の変換ロジックをconvert(Object, TypeDescriptor, TypeDescriptor)に実装します。ソースのTypeDescriptorは変換対象となる値を保持するソースのフィールドにアクセスするのに使います。ターゲットのTypeDescriptorはコンバート後の値をセットするターゲットのフィールドにアクセスするのに使います。

GenericConverterの一例はJava ArrayとCollection間のコンバーターです。ArrayToCollectionConverterはCollectionの要素型を解決するためにターゲットのCollection型を宣言するフィールドの内部状態をチェック(introspects)します。これにより、Collection がターゲットフィールドにセットされる前に、コンバート元のソース配列の各要素をコンバート先のCollectionの要素型に変換可能となります。

GenericConverterは複雑なSPIインタフェースのため、必要な場合にだけ使用してください。一般的な型変換の要求の場合にはConverterかConverterFactoryの方が良いでしょう。

ConditionalGenericConverter

特定の状態がtrueの場合にのみConverterを実行したい場合もあります。たとえば、特定のアノテーションがターゲットフィールドに置かれている場合のみConverterを実行したい、などです。もしくは、特定のメソッド、staticなvalueOfなど、がターゲットクラスに定義されている場合のみ、Converterを実行したい、などです。ConditionalGenericConverterはGenericConverterのサブインタフェースで、これはカスタムマッチング条件を定義可能です。

public interface ConditionalGenericConverter extends GenericConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);

}

ConditionalGenericConverterの一例は永続化エンティティの識別子と参照を変換するEntityConverterです。このEntityConverterはターゲットのエンティテイ型がstaticなfinderメソッド、例えばfindAccount(Long)、を宣言する場合のみマッチします。matches(TypeDescriptor, TypeDescriptor)の実装時にはfinderメソッドのチェックを実行します。

7.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はConverterRegistryを実装しており、これはコンバーターを登録するためのSPIを提供します。内部的には、ConversionService実装は登録されたコンバーターに型変換ロジックの実行を委譲します。

core.convert.supportパッケージで堅牢な(robust)ConversionService実装を提供しています。GenericConversionServiceは汎用目的の実装で多くの環境に適用可能です。ConversionServiceFactoryは共通な*5ConversionService実装を作成するための簡易ファクトリーです。

7.5.5 Configuring a ConversionService

ConversionServicehはアプリケーション開始時にインスタンス化するよう設計されたステートレスオブジェクトなので、複数スレッド間で共有されます。Springアプリケーションでは、通常、Springコンテナ(もしくはApplicationContext)ごとに一つのConversionServiceインスタンスを登録します。このConversionServiceをSpringがピックアップし、フレームワークが型変換を必要とする場合に使います。また、このConversionServiceを何らかのビーンにインジェクトして直接呼び出すことも可能です。

Springに一つもConversionServiceが登録されていない場合、元から有るPropertyEditorベースのシステムが使われます。

SpringにデフォルトのConversionServiceを登録するには、以下のビーン定義をidにconversionServiceを指定して追加します。

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

デフォルトのConversionServiceは、文字列・数値・enum・collection・map・その他の一般的な型、を変換可能です。自前のカスタムコンバーターでデフォルトコンバーターをオーバーライドもしくは追加するには、convertersプロパティを設定します。Property値は、Converter, ConverterFactory, GenericConverter、のいずれかを実装したものを取ります。

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

Spring MVCアプリケーション内ではConversionServiceを良く使います。<mvc:annotation-driven/>の詳細についてはSection 7.6.5, “Configuring Formatting in Spring MVC”を参照してください。

変換にフォーマットを適用したい場合には、FormattingConversionServiceFactoryBeanの詳細に関するSection 7.6.3, “FormatterRegistry SPI”を参照してください。

7.5.6 Using a ConversionService programmatically

プログラム的にConversionServiceインスタンスを使うには、なんらかのビーンに参照をインジェクトします。

@Service
public class MyService {

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

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

ほとんどの場合、targetTypeを指定するconvertメソッドを使いますが、パラメータ化された要素のコレクションなど複雑な型では動作しない場合があります。もし、例えば、プログラム的に IntegerListStringListに変換したい場合、ソースとターゲット型の形式定義をする必要があります。

幸いにも、TypeDescriptorがこれを簡単に行える様々なオプションを提供しています。

DefaultConversionService cs = new DefaultConversionService();

List<Integer> input = ....
cs.convert(input,
    TypeDescriptor.forObject(input), // List<Integer> type descriptor
    TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));

注意点として、DefaultConversionServiceはほとんどの環境において適切なコンバーターを自動的に登録します。これには、コレクションコンバーター・スカラコンバーター・基本的なObjectからStringへのコンバーター、が含まれます。同一のコンバーターDefaultConversionServiceクラスのstaticaddDefaultConvertersメソッドを使用して任意のConverterRegistryに登録可能です。

値型用のコンバーターは配列とコレクションでも使われるため、標準コレクションの処理が適切である限り、SCollectionからTCollectionに変換するコンバータを作成する必要はありません。

7.6 Spring Field Formatting

以前のセクションで述べたように、core.convertは汎用目的の型変換システムです。ある型から別の型への変換ロジック実装用の強い型付Converter SPI同様の統一ConversionService APIを提供します。Springコンテナはこのシステムをビーンプロパティを値にバインドするのに使います。加えて、Spring Expression Language (SpEL)とDataBinder は共にフィールドをバインドするのにこのシステムを使います。たとえば、SpELがexpression.setValue(Object bean, Object value)を行うのにShortLongへ強制する必要がある場合、core.convertシステムが強制を実行します。

webもしくはデスクトップアプリケーション等の一般的なクライアント環境での型変換に対する要求について考えます。そうした環境では、通常、クライアントのポストバック処理サポートに文字列から変換し、同様に、ビューのレンダリング処理にStringを戻します。また、文字列値のローカライズが必要な場合もあります。より汎用なcore.convert Converter SPIでは直接にはそうしたフォーマット(formatting)要求を扱いません。これらを直接扱うため、Spring 3は、クライアント環境におけるPropertyEditorsの替わりとなるシンプルでロバストな機能を提供する、Formatter SPIを導入しています。

通常は、汎用目的の型変換ロジックを実装する必要がある場合にはConverter SPIを使用して下しさい。たとえば、java.util.Dateとjava.lang.Long間の変換などです。クライアント環境で動作させる場合にはFormtter SPIを使用してください。たとえば、webアプリケーションおよび、ローカライズするフィールド値のパースと表示をする必要がある場合などです。ConversionServiceは両SPI用の統一型変換APIを提供します。

7.6.1 Formatter SPI

フィールドのフォーマットロジックを実装するためのFormatter SPIはシンプルで強い型付けがされています。

package org.springframework.format;

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

FormatterはPriterと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を作成するには、単に上記のFormtterインタフェースを実装するだけです。フォーマットしたいオブジェクトの型でTをパラメータ化し、例えば、java.util.Dateなどです。クライアントロケールでTのインスタンスを表示するのにprint()を実装します。クライアントロケールが返すフォーマット表現からTのインスタンスをパースするのにparse()を実装します。Formatterは、もしパースが失敗する場合には、ParseExceptionもしくはIllegalArgumentExceptionをスローすべきです。注意点として、Formatter実装はスレッドセーフにして下さい。

いくつかのFormatter実装をformatサブパッケージで提供しています。numberパッケージは、NumberFormatter, CurrencyFormatterを提供し、また、java.text.NumberFormatを使用してjava.lang.NumberをフォーマットするためのPercentFormatterも提供しています。datetimeパッケージはjava.text.DateFormatjava.util.DateオブジェクトをフォーマットするためのDateFormatterを提供します。datetime.jodaパッケージはJoda Time libraryをベースにした統合datetimeフォーマットを提供しています。

Formatterのサンプル実装を考えてみます。

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;
    }

}

SpringチームはコミュニティドリブンのFormatterコントリビューターを募集しています。コントリビュートするにはjira.spring.ioを参照してください。

7.6.2 Annotation-driven Formatting

これから見ていくように、フィールドフォーマットはフィールド型もしくはアノテーションによる設定が可能です。フォーマッターをアノテーションにバインドするには、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なおです。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;

}
Format Annotation API

ポータブルなフォーマットアノテーションAPIorg.springframework.format.annotationパッケージにあります。@NumberFormatはjava.lang.Numberフィールドをフォーマットするのに使います。@DateTimeFormatは、java.util.Date, java.util.Calendar, java.util.Long, Joda Timeフィールドのフォーマットをするのに使います。

以下の例はIOS Date(yyyy-MM-dd)としてjava.util.Dateをフォーマットするのに@DateTimeFormatを使います。

public class MyModel {

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

}

7.6.3 FormatterRegistry SPI

FormatterRegistryはフォーマッターとコンバーターを登録するためのSPIです。FormattingConversionServiceはFormatterRegistryの実装で様々な環境で使用可能です。この実装はFormattingConversionServiceFactoryBeanを使用することでSpringビーンとして宣言的もしくはプログラム的に設定可能です。また、この実装はConversionServiceを実装しているので、Spring DataBinderとSpring Expression Language (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);

}

上に見るように、フォーマッターはfieldTypeもしくはアノテーションで登録可能です。

FormatterRegistry SPIによって、複数Controllerで重複設定するのではなく、フォーマットのルールの設定を集約可能です。たとえば、すべてのDateフィールドもしくは特定アノテーションを付与したフィールドに、同じルールを適用したい場合などです。共有FormatterRegistryにより、ルールを一度定義するとフォーマットが必要な場所ではそれが適用されます。

7.6.4 FormatterRegistrar SPI

FormatterRegistrarはFormatterRegistry経由でフォーマッターとコンバーターを登録するためのSPIです。

package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);

}

FormatterRegistrarは、Dateのフォーマットなど、フォーマットカテゴリに複数の関連するコンバーターとフォーマッターを登録する場合に使います。また、宣言的な登録が適さない場合にも使います。たとえば、For example when a formatter needs to be indexed under a specific field type different from its own or when registering a Printer/Parser pair. 次のセクションではコンバーターとフォーマッターの登録に関する詳細を解説します。

7.6.5 Configuring Formatting in Spring MVC

Spring MVCアプリケーションでは、MVC名前空間annotation-driven要素の属性として明示的にカスタムConversionServiceインスタンスを登録可能です。設定すると、このConversionServiceはControllerモデルのバインド時に要求される型変換に使われるようになります。明示的に設定しない場合、Spring MVCは数値と日付など一般的な型に使われるデフォルトフォーマッターとコンバーターを自動的に登録します。

デフォルトのフォーマットルールを使うには、Spring 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.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven/>

</beans>

上記の一行の設定により、NumberとDate用のデフォルトフォーマッターがインストールされ、@NumberFormatと@DateTimeFormatアノテーションのサポートも含まれます。また、Joda Timeがクラスパス上にあれば、Joda Timeフォーマットライブラリ用の完全なサポートもインストールされます。

カスタムフォーマッターとコンバーターでConversionServiceインスタンスをインジェクトするには、conversion-service属性を設定して、コンバーターやフォーマッターやFormattingConversionServiceFactoryBeanのプロパティとしてのFormatterRegistrarsを指定します。

<?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.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.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>

FormatterRegistrarsを使用する場合の詳細な情報についてはFormattingConversionServiceFactoryBeanとSection 7.6.4, “FormatterRegistrar SPI”を参照してください。

7.7 Configuring a global date & time format

デフォルトでは、@DateTimeFormatを付与していないdateとtimeのフィールドはDateFormat.SHORTスタイルで文字列から変換します。これは自前のグローバルフォーマットを定義することで変更が可能です。

Springがデフォルトフォーマッターを登録しないことを保証する必要がある場合、代わりに手動ですべてのフォーマッターを登録しなければなりません。org.springframework.format.datetime.joda.JodaTimeFormatterRegistrarorg.springframework.format.datetime.DateFormatterRegistrarクラスを、Joda Timeライブラリを使うかどうかに応じて使い分けてください。

たとえば、以下のJavaコンフィグレーションはグローバルフォーマット'yyyyMMdd'を登録します。この例はJoda Timeライブラリには依存しません。

@Configuration
public class AppConfig {

    @Bean
    public FormattingConversionService conversionService() {

        // Use the DefaultFormattingConversionService but do not register defaults
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);

        // Ensure @NumberFormat is still supported
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());

        // Register date conversion with a specific global format
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter("yyyyMMdd"));
        registrar.registerFormatters(conversionService);

        return conversionService;
    }
}

XMLで設定したい場合はFormattingConversionServiceFactoryBeanを使います。以下は同様なサンプルで、Joda Timeを使っています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    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.xsd>

    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="registerDefaultFormatters" value="false" />
        <property name="formatters">
            <set>
                <bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" />
            </set>
        </property>
        <property name="formatterRegistrars">
            <set>
                <bean class="org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar">
                    <property name="dateFormatter">
                        <bean class="org.springframework.format.datetime.joda.DateTimeFormatterFactoryBean">
                            <property name="pattern" value="yyyyMMdd"/>
                        </bean>
                    </property>
                </bean>
            </set>
        </property>
    </bean>
</beans>

Joda Timeは、date, time, date-timeを表現するために、別々の型を用意しています。JodaTimeFormatterRegistrardateFormatter, timeFormatter, dateTimeFormatterプロパティにそれぞれの型に応じたフォーマットを設定してください。DateTimeFormatterFactoryBeanはフォーマッターを生成する方法を提供しています。

Spring MVCを使う場合、conversion serviceを明示的に設定することを覚えておいて下さい。Java@Configurationでは、WebMvcConfigurationSupportの拡張とmvcConversionService()メソッドのオーバーライドします。XMLでは、mvc:annotation-driven要素の'conversion-service'属性を使います。詳細についてはSection 7.6.5, “Configuring Formatting in Spring MVC”を参照してください。

7.8 Spring Validation

Spring 3ではvalidationサポートにいくつかの拡張を行っています。まず、JSR-303 Bean Validation APIを完全にサポートしました。次に、プログラム的に使う場合、SpringのDataBinderでバインドとオブジェクトのvalidateを行えます。最後に、Spring MVC@Controllerの入力値の宣言的なvalidateをサポートしました。

7.8.1 Overview of the JSR-303 Bean Validation API

JSR-303はJavaプラットフォームにおけるvalidation制約の宣言とメタデータを標準化しています。このAPIは、ドメインモデルのプロパティに宣言的なvalidation制約をアノテーションで行い、それを実行時に強制します。利用可能な組み込み制約が多数用意されています。また、カスタム制約を定義可能です。

説明のため、簡単な二つのプロパティから成るPersonFormを考えます。

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

JSR-303はプロパティに対して宣言的なvalidation制約を定義します。

public class PersonForm {

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

    @Min(0)
    private int age;

}

このクラスのインスタンス作成時にJSR-303 Validatorがvalidateを行い、制約を強制します。

JSR-303/JSR-349に関する情報については、Bean Validation websiteを参照してください。デフォルトの参照実装固有の機能に関する情報については、Hibernate Validatorを参照してください。SpringビーンとしてBean Validationプロバイダをセットアップする方法を知るにはこのまま読み進めてください。

7.8.2 Configuring a Bean Validation Provider

SpringはBean Validation APIを完全にサポートしています。これにはSpringビーンとしてJSR-303/JSR-349 Bean Validationプロバイダを起動するためのサポートも含まれています。これにより、アプリケーションでvalidationが必要な場所でjavax.validation.ValidatorFactoryもしくはjavax.validation.Validatorをインジェクト可能です。

SpringビーンとしてデフォルトのValidatorを設定するにはLocalValidatorFactoryBeanを使います。

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

上記の設定はデフォルトの起動メカニズムを使用してBean Validationの初期化をトリガします。Hibernate ValidatorなどのJSR-303/JSR-349プロバイダはクラスパスに設定されているのが前提条件で、自動的に検出が行われます。

Injecting a Validator

LocalValidatorFactoryBeanは、Springのorg.springframework.validation.Validatorと、javax.validation.ValidatorFactoryおよびjavax.validation.Validatorを実装しています。validationロジックを実行する必要のあるビーンに、これらのインタフェースの参照をインジェクト出来ます。

Bean Validation APIを直接使いたい場合はjavax.validation.Validatorの参照をインジェクトします。

import javax.validation.Validator;

@Service
public class MyService {

    @Autowired
    private Validator validator;

ビーンがSpring Validation APIを要求する場合はorg.springframework.validation.Validatorの参照をインジェクトします。

import org.springframework.validation.Validator;

@Service
public class MyService {

    @Autowired
    private Validator validator;

}
Configuring Custom Constraints

Bean Validation制約は二つの要素から成ります。一つ目は@Constraintで、制約と設定可能なプロパティを宣言するアノテーションです。二つ目はjavax.validation.ConstraintValidatorの実装で、制約の振る舞いを実装します。実装と宣言を関連付けるには、@Constraintから対応するValidationConstraint実装クラスへの参照を行います。実行時に、制約アノテーションドメインモデルで検出されるとConstraintValidatorFactoryが参照されている実装をインスタンス化します。

デフォルトでは、LocalValidatorFactoryBeanは、ConstraintValidatorインスタンスを作成するのにSpringを使用するSpringConstraintValidatorFactoryを、設定します。これにより、カスタムのConstraintValidatorsでDIを利用可能です。

以下はカスタム@Constraint宣言の例で、その次は宣言に関連付けられたConstraintValidatorの実装で、SpringのDIを使っています。

@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実装は他のSpringビーン同様に@AutowiredでDIが可能です。

Spring-driven Method Validation

Bean Validation 1.1はメソッドvalidation機能をサポートし、また、Hibernate Validator 4.3はカスタム拡張(custom extension)をサポートしています。これらはMethodValidationPostProcessorビーン定義経由でSpringコンテキストに統合可能です。

<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

Springドリブンのメソッドvalidationを有効にするには、すべてのターゲットクラスにSpringの@Validatedを付与する必要があり、オプションでvalidationグループの宣言を使うことも出来ます*6Hibernate ValidatorとBean Validation 1.1プロバイダをセットアップするための詳細についてはMethodValidationPostProcessorjavadocを参照してください。

Additional Configuration Options

デフォルトのLocalValidatorFactoryBeanは様々なケースで役に立つような設定になっています。Bean Validationの生成には様々な設定オプションがあり、from message interpolation to traversal resolution*7. オプションの詳細についてはLocalValidatorFactoryBeanjavadocを参照してください。

7.8.3 Configuring a DataBinder

Spring 3以降では、DataBinderインスタンスとValidatorを組み合わせことが可能です。一度設定を行うと、Validatorがbinder.validate()ごとに実行されます。何らかのvalidationエラーは自動的にbinderのBindingResultに追加されます。

プログラム的にDataBinderを動かすには、ターゲットオブジェクトのバインディング後にvalidationロジックを呼び出します。

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

// bind to the target object
binder.bind(propertyValues);

// validate the target object
binder.validate();

// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();

また、DataBinderはdataBinder.addValidatorsおよびdataBinder.replaceValidators経由で複数Validatorを組み合わせ可能です。DataBinderインスタンスにローカルで設定したSpringのValidatorと、グローバルに設定したBean Validationを組み合わせる際に役立ちます。詳細はthe section called “Configuring a Validator for use by Spring MVC”を参照してください。

7.8.4 Spring MVC 3 Validation

Spring 3から始める場合、Spring MVCには@Controllerの入力を自動的にvalidateする機能があります。以前のバージョンではvalidationロジックをマニュアルで実行する責任は開発者にありました。

Triggering @Controller Input Validation

@Controllerの入力のvalidatoionをトリガするには、入力引数に@Validを付与します。

@Controller
public class MyController {

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

Spring MVCは、設定したValidatorをバインディング後に、@Validを付与したオブジェクトをvalidateします。

@Validaアノテーションは標準JSR-303 Bean Validation APIの一部であり、Spring固有のものではありません。

Configuring a Validator for use by Spring MVC

@Validメソッド引数が検出された場合にValidatorインスタンスが呼ばれるよう設定する方法は二種類あります。一つ目は、@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) { ... }

}

二つ目は、グローバルのWebBindingInitializersetValidator(Validator)を呼び方法です。これを使うことで、すべての@Controllerクラスに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.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

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

</beans>

グローバルとローカルのvalidatorを組み合わせるには、上記の設定のようにグローバルなvalidatorを設定したあと、以下のようにローカルのvalidatorを追加します。

@Controller
public class MyController {

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

}
Configuring a JSR-303/JSR-349 Validator for use by Spring MVC

Bean Validationでは、通常、単一のjavax.validation.Validatorインスタンスがvalidation制約を宣言するすべてのモデルオブジェクトをvalidateします。Spring MVCにJSR-303 Validatorを設定するには、Hibernate ValidatorなどのBean Validationプロバイダをクラスパスに追加するだけです。Spring MVCはクラスパスのプロバイダを検出し、すべてのContollerでBean Validationを自動的に有効化します。

Bean Validationサポートを有効化するために必要な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.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

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

</beans>

このほんのちょっとの設定により、@Valid @Controllerの入力を検出した場合、Bean Validationプロバイダがvalidateを行います。プロバイダが入力に対して宣言した何らかの制約を強制します。標準Spring MVCフォームタグでレンダリング可能なBindingResultのエラーとしてConstraintViolationsが自動的に公開されます。

*1:何の事?

*2:themはpros and consかと思い、『賛否のうち一つを排除するわけではない』、って訳したけど、意味が分からない

*3:convert the human readable form back to the original date が原文。『相互変換』はちょっと意訳し過ぎだろうか?

*4:externalized bean property value stringsたぶんXMLとかでプロパティ値を定義するヤツのことと思われ

*5:commonが原文だが、「共通」というより「良くありがちな~」「一般的な~」というニュアンスかもしれない

*6:optionally declaring the validation groups to useが原文。validation groupsっていうくらいだから、クラスごとじゃなく、複数クラスにまとめて同じアノテーションを付与できる機能があるのだろうか?

*7:よくわからん