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
を提供しています。Validator
とDataBinder
はvalidation
パッケージに含まれており、主にMVCフレームワークで使われますがそれ以外でも使用可能です。
BeanWrapper
はSpring 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"); } } }
上記の通り、ValidationUtils
のstatic
rejectIfEmpty(..)
は、もし'name'
プロパティがnull
もしくは空文字の場合は拒否するために使います。上記のサンプル以外の機能についてはValidationUtils
のjavadocを参照してください。
複雑なオブジェクトでのネスト化されたオブジェクトそれぞれを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のPropertyChangeListeners
とVetoableChangeListeners
を追加可能ですが、ターゲットクラスにそのためのコードを書く必要はありません。最後に重要な点として、BeanWrapper
はインデックス付きプロパティ(indexed properties)の設定機能を提供します。BeanWrapper
は通常はアプリケーションコードから直接使いませんが、DataBinder
とBeanFactory
はその限りではありません。
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] |
account MapプロパティのキーCOMPANYNAMEでマッピングしたエントリの値を指定 |
以下にBeanWrapper
でプロパティ設定と取得をする例を示します。
(以降の記述は、BeanWrapper
を直接使おうとしない限り、全く不要です。DataBinder
とBeanFactory
とその実装を使用する場合、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; } }
以下のコードはインスタンス化したCompanies
とEmployees
のプロパティの検索と操作をする例を示しています。
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はObject
とString
間の変換にPropertyEditors
を使います。オブジェクト自身とは異なる方法でプロパティを表現出来ると便利な場合があります。元々の日付と人間が読める形式が相互に変換して元に戻せる場合*3、Date
は人間が読める形式(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
パッケージのjavadocのPropertyEditors
を参照してください。
Springで使われるプロパティ編集の例は以下の通りです。
- ビーンのプロパティ設定(setting properties on beans)は
PropertyEditors
を使用して行います。XMLファイルに宣言するビーンのプロパティ値としてjava.lang.String
で指定する場合、Springは(もし対応プロパティのsetterがClass-
パラメータを持つ場合)パラメータのClass
オブジェクトへと解決を試みるのにClassEditor
を使います。 - Spring MVCフレームワークのHTTPリクエストパラメータのパース(parsing HTTP request parameters)は
PropertyEditors
の色々な種類を使用して行います。PropertyEditors
はCommandController
の全サブクラスに手動でバインド可能です。
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 を生成する(中間にResourceEditor とResource を経由)機能を持ち、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
を使います。また、Font
やColor
と多数のプリミティブ型などのPropertyEditor
を含むsun.bean.editors
が検索パスには含まれます。標準JavaBeansインフラは自動的にPropertyEditor
クラスを(明示的な登録をしなくても)検索しますが、その条件は、対象クラスと同一パッケージで'Editor'
を追加したクラス名にします。たとえば、以下のようなクラスとパッケージ構造のとき、Foo
型のプロパティ用のPropertyEditor
として使われるのはFooEditor
となります。
com chank pop Foo FooEditor // Fooクラス用のPropertyEditor
なお、同様な方法で標準BeanInfo
JavaBeansメカニズムを使うことも出来ます(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
は、ビーンファクトリーポストプロセッサーで、ApplicationContext
へPropertyEditor
インスタンス用の便利な追加サポートを加えるのに使われます。
いま、ユーザが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プロパティを割り当てたい場合、裏側でPropertyEditor
がExoticType
インスタンスへの変換を行います。
<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
を生成して使う方法があります。このインタフェースは複数の異なる状況でプロパティエディタの同じ組み合わせを使いたい場合に特に役立ちます。PropertyEditorRegistrars
はPropertyEditorRegistry
というインタフェースと組み合わせて動作し、このインタフェースはSpringのBeanWrapper
が実装します。PropertyEditorRegistrars
はCustomEditorConfigurer
(解説はコチラ)と組み合わせる場合に特に便利で、setPropertyEditorRegistrars(..)
を公開しています。この方法でCustomEditorConfigurer
に追加したPropertyEditorRegistrars
はDataBinder
と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を使用する場合、データバインディングControllers
(SimpleFormController
など)と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
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()); } } }
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
メソッドを使いますが、パラメータ化された要素のコレクションなど複雑な型では動作しない場合があります。もし、例えば、プログラム的に Integer
のList
をString
のList
に変換したい場合、ソースとターゲット型の形式定義をする必要があります。
幸いにも、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
クラスのstaticなaddDefaultConverters
メソッドを使用して任意のConverterRegistry
に登録可能です。
値型用のコンバーターは配列とコレクションでも使われるため、標準コレクションの処理が適切である限り、S
のCollection
からT
のCollection
に変換するコンバータを作成する必要はありません。
7.6 Spring Field Formatting
以前のセクションで述べたように、core.convert
は汎用目的の型変換システムです。ある型から別の型への変換ロジック実装用の強い型付Converter SPI同様の統一ConversionService APIを提供します。Springコンテナはこのシステムをビーンプロパティを値にバインドするのに使います。加えて、Spring Expression Language (SpEL)とDataBinder は共にフィールドをバインドするのにこのシステムを使います。たとえば、SpELがexpression.setValue(Object bean, Object value)
を行うのにShort
をLong
へ強制する必要がある場合、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.DateFormat
でjava.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
ポータブルなフォーマットアノテーションAPIはorg.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
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.JodaTimeFormatterRegistrar
かorg.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
を表現するために、別々の型を用意しています。JodaTimeFormatterRegistrar
のdateFormatter
, 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グループの宣言を使うことも出来ます*6。Hibernate ValidatorとBean Validation 1.1プロバイダをセットアップするための詳細についてはMethodValidationPostProcessor
のjavadocを参照してください。
Additional Configuration Options
デフォルトのLocalValidatorFactoryBean
は様々なケースで役に立つような設定になっています。Bean Validationの生成には様々な設定オプションがあり、from message interpolation to traversal resolution*7. オプションの詳細についてはLocalValidatorFactoryBean
のjavadocを参照してください。
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) { ... } }
二つ目は、グローバルのWebBindingInitializer
でsetValidator(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:よくわからん