データバインド

データバインディングは、JavaBeans の規則に従って、ユーザー入力がキーとしてプロパティパスを持つマップであるターゲットオブジェクトにユーザー入力をバインドできます。DataBinder はこれをサポートするメインクラスであり、ユーザー入力をバインドする 2 つのメソッドを提供します。

  • コンストラクターバインディング - ユーザー入力をパブリックデータコンストラクターにバインドし、ユーザー入力内のコンストラクター引数の値を検索します。

  • プロパティのバインド - ユーザー入力を setter にバインドし、ユーザー入力のキーをターゲットオブジェクト構造のプロパティに照合します。

コンストラクターとプロパティバインディングの両方を適用することも、1 つだけを適用することもできます。

コンストラクターのバインド

コンストラクターバインディングを使用するには:

  1. null をターゲットオブジェクトとして DataBinder を作成します。

  2. targetType をターゲットクラスに設定します。

  3. construct を呼び出します。

ターゲットクラスには、引数を持つ単一のパブリックコンストラクターまたは単一の非パブリックコンストラクターが必要です。複数のコンストラクターがある場合は、デフォルトのコンストラクター (存在する場合) が使用されます。

デフォルトでは、コンストラクターのパラメーター名は引数の値を検索するために使用されますが、NameResolver を構成することもできます。Spring MVC と WebFlux はどちらも、コンストラクターパラメーターの @BindParam アノテーションを介してバインドする値の名前をカスタマイズできるようにすることに依存しています。

型変換は、ユーザー入力を変換するために必要に応じて適用されます。コンストラクターパラメーターがオブジェクトの場合、同じ方法で再帰的に構築されますが、ネストされたプロパティパスを使用します。つまり、コンストラクターバインディングはターゲットオブジェクトとそれに含まれるオブジェクトの両方を作成します。

バインディングおよび変換エラーは、DataBinder の BindingResult に反映されます。ターゲットが正常に作成されると、construct の呼び出し後に、作成されたインスタンスに target が設定されます。

BeanWrapper によるプロパティバインディング

org.springframework.beans パッケージは、JavaBeans 標準に準拠しています。JavaBean は、デフォルトの引数なしのコンストラクターを持つクラスであり、命名規則に従います(たとえば、bingoMadness という名前のプロパティには setter メソッド setBingoMadness(..) および getter メソッド getBingoMadness() があります)。JavaBeans と仕様の詳細については、javabeans (標準 Javadoc) を参照してください。

Bean パッケージの非常に重要なクラスの 1 つは、BeanWrapper インターフェースとそれに対応する実装(BeanWrapperImpl)です。javadoc から引用されているように、BeanWrapper は、プロパティ値の設定と取得(個別または一括)、プロパティ記述子の取得、プロパティのクエリを行って読み取り可能または書き込み可能かどうかを判断する機能を提供します。また、BeanWrapper はネストされたプロパティをサポートし、サブプロパティのプロパティを無制限の深さに設定できます。BeanWrapper は、ターゲットクラスのコードをサポートする必要なく、標準 JavaBeans PropertyChangeListeners および VetoableChangeListeners を追加する機能もサポートしています。最後になりましたが、BeanWrapper はインデックス付きプロパティの設定をサポートします。BeanWrapper は通常、アプリケーションコードによって直接使用されるのではなく、DataBinder および BeanFactory によって使用されます。

BeanWrapper の動作方法は、その名前によって部分的に示されます。Bean をラップして、プロパティの設定や取得など、その Bean でアクションを実行します。

基本およびネストされたプロパティの設定と取得

プロパティの設定と取得は、setPropertyValue および getPropertyValue のオーバーロードされた BeanWrapper のメソッドバリアントを介して行われます。詳細については、Javadoc を参照してください。次の表に、これらの規則の例をいくつか示します。

表 1: プロパティの例
説明

name

getName() または isName() および setName(..) メソッドに対応するプロパティ name を示します。

account.name

(たとえば) getAccount().setName() または getAccount().getName() メソッドに対応するプロパティ account のネストされたプロパティ name を示します。

account[2]

インデックス付きプロパティ account3 番目の要素を示します。インデックス付きプロパティは、型 arraylist、その他の自然に順序付けられたコレクションにすることができます。

account[COMPANYNAME]

accountMap プロパティの COMPANYNAME キーによって索引付けされたマップ項目の値を示します。

BeanWrapper を直接使用する予定がない場合、この次のセクションはそれほど重要ではありません。DataBinder と BeanFactory とそれらのデフォルト実装のみを使用する場合は、PropertyEditorsセクションに進んでください

次の 2 つのサンプルクラスは、BeanWrapper を使用してプロパティを取得および設定します。

  • Java

  • Kotlin

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;
	}
}
class Company {
	var name: String? = null
	var managingDirector: Employee? = null
}
  • Java

  • Kotlin

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;
	}
}
class Employee {
	var name: String? = null
	var salary: Float? = null
}

次のコードスニペットは、インスタンス化された Company および Employee のプロパティの一部を取得および操作する方法の例を示しています。

  • Java

  • Kotlin

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

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

// retrieving the salary of the managingDirector through the company
val salary = company.getPropertyValue("managingDirector.salary") as Float?

PropertyEditor さん

Spring は、PropertyEditor の概念を使用して、Object と String の間の変換を行います。オブジェクト自体とは異なる方法でプロパティを表すと便利です。例: Date は人間が読める形式で表現できます(String'2007-14-09' のように)が、人間が読める形式を元の日付に戻すことができます(あるいは、人間が読める形式で入力した日付を戻すことができます) Date オブジェクトへ)。この動作は、型 java.beans.PropertyEditor のカスタムエディターを登録することで実現できます。BeanWrapper または特定の IoC コンテナー(前の章で説明)でカスタムエディターを登録すると、プロパティを目的の型に変換する方法がわかります。PropertyEditor の詳細については、Oracle の java.beans パッケージの javadoc を参照してください。

Spring でプロパティの編集が使用されるいくつかの例:

  • Bean のプロパティの設定は、PropertyEditor 実装を使用して行われます。XML ファイルで宣言する Bean のプロパティの値として String を使用する場合、Spring(対応するプロパティの setter に Class パラメーターがある場合)は、ClassEditor を使用してパラメーターを Class オブジェクトに解決しようとします。

  • Spring の MVC フレームワークでの HTTP リクエストパラメーターの解析は、CommandController のすべてのサブクラスで手動でバインドできる、あらゆる種類の PropertyEditor 実装を使用して行われます。

Spring には、PropertyEditor が多数実装されており、簡単に作業できます。それらはすべて org.springframework.beans.propertyeditors パッケージにあります。ほとんど(すべてではありませんが、次の表に示すように)は、デフォルトで BeanWrapperImpl によって登録されています。プロパティエディターを何らかの方法で構成できる場合でも、独自のバリアントを登録してデフォルトのバリアントをオーバーライドできます。次の表は、Spring が提供するさまざまな PropertyEditor 実装を示しています。

表 2: ビルトイン PropertyEditor 実装
クラス 説明

ByteArrayPropertyEditor

バイト配列のエディター。文字列を対応するバイト表現に変換します。BeanWrapperImpl によってデフォルトで登録されています。

ClassEditor

クラスを実際のクラスに、またはその逆に表す文字列を解析します。クラスが見つからない場合、IllegalArgumentException がスローされます。デフォルトでは、BeanWrapperImpl によって登録されます。

CustomBooleanEditor

Boolean プロパティ用のカスタマイズ可能なプロパティエディター。デフォルトでは、BeanWrapperImpl によって登録されますが、そのカスタムインスタンスをカスタムエディターとして登録することでオーバーライドできます。

CustomCollectionEditor

ソース Collection を特定のターゲット Collection 型に変換するコレクションのプロパティエディター。

CustomDateEditor

java.util.Date 用のカスタマイズ可能なプロパティエディター、カスタム DateFormat をサポートします。デフォルトでは登録されていません。必要に応じて適切な形式でユーザー登録する必要があります。

CustomNumberEditor

IntegerLongFloat や Double など、Number サブクラスのカスタマイズ可能なプロパティエディター。デフォルトでは、BeanWrapperImpl によって登録されますが、そのカスタムインスタンスをカスタムエディターとして登録することでオーバーライドできます。

FileEditor

文字列を java.io.File オブジェクトに解決します。デフォルトでは、BeanWrapperImpl によって登録されます。

InputStreamEditor

文字列を取得し、InputStream プロパティを文字列として直接設定できるように、ResourceEditor および Resource を介して InputStream を生成できる一方向プロパティエディター。デフォルトの使用箇所では、InputStream が閉じられないことに注意してください。デフォルトでは、BeanWrapperImpl によって登録されます。

LocaleEditor

文字列を Locale オブジェクトに、またはその逆に解決できます(文字列形式は [language]_[country]_[variant] であり、Locale の toString() メソッドと同じです)。アンダースコアの代わりに、区切り文字としてスペースも使用できます。デフォルトでは、BeanWrapperImpl によって登録されます。

PatternEditor

文字列を java.util.regex.Pattern オブジェクトに、またはその逆に解決できます。

PropertiesEditor

文字列(java.util.Properties クラスの javadoc で定義された形式でフォーマットされた)を Properties オブジェクトに変換できます。デフォルトでは、BeanWrapperImpl によって登録されます。

StringTrimmerEditor

文字列をトリムするプロパティエディター。オプションで、空の文字列を null 値に変換できます。デフォルトでは登録されていません — ユーザー登録する必要があります。

URLEditor

URL の文字列表現を実際の URL オブジェクトに解決できます。デフォルトでは、BeanWrapperImpl によって登録されます。

Spring は、java.beans.PropertyEditorManager を使用して、必要になる可能性のあるプロパティエディターの検索パスを設定します。検索パスには sun.bean.editors も含まれます。これには、FontColor、ほとんどのプリミティブ型などの型の PropertyEditor 実装が含まれます。また、標準の JavaBeans インフラストラクチャは、PropertyEditor クラスが処理するクラスと同じパッケージにあり、そのクラスと同じ名前で Editor が追加されている場合、自動的に検出します(明示的に登録する必要はありません)。例: 次のクラスとパッケージ構造を持つことができます。これは、SomethingEditor クラスが Something -typed プロパティの PropertyEditor として認識され、使用されるのに十分です。

com
  chank
    pop
      Something
      SomethingEditor // the PropertyEditor for the Something class

ここでも標準の BeanInfo JavaBeans メカニズムを使用できることに注意してください(ここである程度説明します)。次の例では、BeanInfo メカニズムを使用して、1 つ以上の PropertyEditor インスタンスを関連するクラスのプロパティに明示的に登録します。

com
  chank
    pop
      Something
      SomethingBeanInfo // the BeanInfo for the Something class

参照される SomethingBeanInfo クラスの次の Java ソースコードは、CustomNumberEditor を Something クラスの age プロパティに関連付けます。

  • Java

  • Kotlin

public class SomethingBeanInfo extends SimpleBeanInfo {

	public PropertyDescriptor[] getPropertyDescriptors() {
		try {
			final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
			PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
				@Override
				public PropertyEditor createPropertyEditor(Object bean) {
					return numberPE;
				}
			};
			return new PropertyDescriptor[] { ageDescriptor };
		}
		catch (IntrospectionException ex) {
			throw new Error(ex.toString());
		}
	}
}
class SomethingBeanInfo : SimpleBeanInfo() {

	override fun getPropertyDescriptors(): Array<PropertyDescriptor> {
		try {
			val numberPE = CustomNumberEditor(Int::class.java, true)
			val ageDescriptor = object : PropertyDescriptor("age", Something::class.java) {
				override fun createPropertyEditor(bean: Any): PropertyEditor {
					return numberPE
				}
			}
			return arrayOf(ageDescriptor)
		} catch (ex: IntrospectionException) {
			throw Error(ex.toString())
		}

	}
}

カスタム PropertyEditor

Bean プロパティを文字列値として設定する場合、Spring IoC コンテナーは最終的に標準 JavaBeans PropertyEditor 実装を使用して、これらの文字列をプロパティの複雑な型に変換します。Spring は、多数のカスタム PropertyEditor 実装を事前登録します(たとえば、ストリングとして表現されたクラス名を Class オブジェクトに変換するため)。さらに、Java の標準 JavaBeans PropertyEditor ルックアップメカニズムにより、クラスの PropertyEditor に適切な名前を付けて、サポートを提供するクラスと同じパッケージに配置して、自動的に検出できるようにします。

他のカスタム PropertyEditors を登録する必要がある場合、いくつかのメカニズムが利用可能です。通常、便利または推奨されない最も手動のアプローチは、BeanFactory 参照があると仮定して、ConfigurableBeanFactory インターフェースの registerCustomEditor() メソッドを使用することです。別の(少し便利な)メカニズムは、CustomEditorConfigurer と呼ばれる特別な Bean ファクトリポストプロセッサーを使用することです。Bean ファクトリポストプロセッサーは BeanFactory 実装で使用できますが、CustomEditorConfigurer にはネストされたプロパティ設定があるため、ApplicationContext で使用することを強くお勧めします。他の Bean と同様の方法でデプロイできます。自動的に検出および適用されます。

すべての Bean ファクトリとアプリケーションコンテキストは、BeanWrapper を使用してプロパティ変換を処理することにより、多数の組み込みプロパティエディターを自動的に使用することに注意してください。BeanWrapper が登録する標準プロパティエディターは、前のセクションにリストされています。さらに、ApplicationContext は、特定のアプリケーションコンテキスト型に適切な方法でリソースルックアップを処理するために、エディターをオーバーライドまたは追加します。

標準の JavaBeans PropertyEditor インスタンスは、文字列として表現されたプロパティ値をプロパティの実際の複合型に変換するために使用されます。Bean ファクトリポストプロセッサーである CustomEditorConfigurer を使用して、ApplicationContext に追加の PropertyEditor インスタンスのサポートを簡単に追加できます。

ExoticType というユーザークラスと、ExoticType をプロパティとして設定する必要がある DependsOnExoticType という別のクラスを定義する次の例を考えてみましょう。

  • Java

  • Kotlin

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

class ExoticType(val name: String)

class DependsOnExoticType {

	var type: ExoticType? = null
}

物事が適切に設定されたら、type プロパティを文字列として割り当てることができます。これは、PropertyEditor が実際の ExoticType インスタンスに変換します。次の Bean 定義は、この関連をセットアップする方法を示しています。

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

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

  • Java

  • Kotlin

package example;

import java.beans.PropertyEditorSupport;

// converts string representation to ExoticType object
public class ExoticTypeEditor extends PropertyEditorSupport {

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

import java.beans.PropertyEditorSupport

// converts string representation to ExoticType object
class ExoticTypeEditor : PropertyEditorSupport() {

	override fun setAsText(text: String) {
		value = ExoticType(text.toUpperCase())
	}
}

最後に、次の例は、CustomEditorConfigurer を使用して新しい PropertyEditor を ApplicationContext に登録する方法を示しています。これにより、必要に応じて PropertyEditor を使用できるようになります。

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

PropertyEditorRegistrar

プロパティエディターを Spring コンテナーに登録するためのもう 1 つのメカニズムは、PropertyEditorRegistrar を作成して使用することです。このインターフェースは、いくつかの異なる状況で同じプロパティエディターのセットを使用する必要がある場合に特に便利です。対応するレジストラを作成し、それぞれの場合に再利用できます。PropertyEditorRegistrar インスタンスは、PropertyEditorRegistry というインターフェースと連携して動作します。このインターフェースは、Spring BeanWrapper (および DataBinder) によって実装されます。PropertyEditorRegistrar インスタンスは、setPropertyEditorRegistrars(..) と呼ばれるプロパティを公開する CustomEditorConfigurer (ここで説明) と組み合わせて使用すると特に便利です。この方法で CustomEditorConfigurer に追加された PropertyEditorRegistrar インスタンスは、DataBinder および Spring MVC コントローラーと簡単に共有できます。さらに、カスタムエディターでの同期の必要性が回避されます。PropertyEditorRegistrar は、Bean 作成試行ごとに新しい PropertyEditor インスタンスを作成することが期待されます。

次の例は、独自の PropertyEditorRegistrar 実装を作成する方法を示しています。

  • Java

  • Kotlin

package com.foo.editors.spring;

public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

	public void registerCustomEditors(PropertyEditorRegistry registry) {

		// it is expected that new PropertyEditor instances are created
		registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());

		// you could register as many custom property editors as are required here...
	}
}
package com.foo.editors.spring

import org.springframework.beans.PropertyEditorRegistrar
import org.springframework.beans.PropertyEditorRegistry

class CustomPropertyEditorRegistrar : PropertyEditorRegistrar {

	override fun registerCustomEditors(registry: PropertyEditorRegistry) {

		// it is expected that new PropertyEditor instances are created
		registry.registerCustomEditor(ExoticType::class.java, ExoticTypeEditor())

		// you could register as many custom property editors as are required here...
	}
}

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 の MVC Web フレームワークを使用している人にとっては、データバインディング Web コントローラーと組み合わせて PropertyEditorRegistrar を使用すると非常に便利です。次の例では、@InitBinder メソッドの実装で PropertyEditorRegistrar を使用しています。

  • Java

  • Kotlin

@Controller
public class RegisterUserController {

	private final PropertyEditorRegistrar customPropertyEditorRegistrar;

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

	@InitBinder
	void initBinder(WebDataBinder binder) {
		this.customPropertyEditorRegistrar.registerCustomEditors(binder);
	}

	// other methods related to registering a User
}
@Controller
class RegisterUserController(
	private val customPropertyEditorRegistrar: PropertyEditorRegistrar) {

	@InitBinder
	fun initBinder(binder: WebDataBinder) {
		this.customPropertyEditorRegistrar.registerCustomEditors(binder)
	}

	// other methods related to registering a User
}

このスタイルの PropertyEditor 登録は、簡潔なコードにつながる可能性があり(@InitBinder メソッドの実装は 1 行のみです)、一般的な PropertyEditor 登録コードをクラスにカプセル化して、必要な数のコントローラー間で共有できます。