データバインド
データバインディングは、JavaBeans の規則に従って、ユーザー入力がキーとしてプロパティパスを持つマップであるターゲットオブジェクトにユーザー入力をバインドできます。DataBinder
はこれをサポートするメインクラスであり、ユーザー入力をバインドする 2 つのメソッドを提供します。
コンストラクターバインディング - ユーザー入力をパブリックデータコンストラクターにバインドし、ユーザー入力内のコンストラクター引数の値を検索します。
プロパティのバインド - ユーザー入力を setter にバインドし、ユーザー入力のキーをターゲットオブジェクト構造のプロパティに照合します。
コンストラクターとプロパティバインディングの両方を適用することも、1 つだけを適用することもできます。
コンストラクターのバインド
コンストラクターバインディングを使用するには:
null
をターゲットオブジェクトとしてDataBinder
を作成します。targetType
をターゲットクラスに設定します。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 を参照してください。次の表に、これらの規則の例をいくつか示します。
式 | 説明 |
---|---|
|
|
| (たとえば) |
| インデックス付きプロパティ |
|
|
(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
実装を示しています。
クラス | 説明 |
---|---|
| バイト配列のエディター。文字列を対応するバイト表現に変換します。 |
| クラスを実際のクラスに、またはその逆に表す文字列を解析します。クラスが見つからない場合、 |
|
|
| ソース |
|
|
|
|
| 文字列を |
| 文字列を取得し、 |
| 文字列を |
| 文字列を |
| 文字列( |
| 文字列をトリムするプロパティエディター。オプションで、空の文字列を |
| URL の文字列表現を実際の |
Spring は、java.beans.PropertyEditorManager
を使用して、必要になる可能性のあるプロパティエディターの検索パスを設定します。検索パスには sun.bean.editors
も含まれます。これには、Font
、Color
、ほとんどのプリミティブ型などの型の 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
登録コードをクラスにカプセル化して、必要な数のコントローラー間で共有できます。