資料綁定
資料綁定適用於將使用者輸入綁定到目標物件,其中使用者輸入是一個以屬性路徑作為鍵的 Map,並遵循 JavaBeans 慣例。DataBinder
是支援此功能的主要類別,它提供兩種綁定使用者輸入的方式
您可以同時應用建構子和屬性綁定,或僅應用其中一種。
建構子綁定
要使用建構子綁定
-
建立一個以
null
作為目標物件的DataBinder
。 -
將
targetType
設定為目標類別。 -
呼叫
construct
。
目標類別應該具有單一公用建構子或單一帶有引數的非公用建構子。如果有多個建構子,則會使用預設建構子 (如果存在)。
預設情況下,引數值會透過建構子參數名稱查找。Spring MVC 和 WebFlux 透過建構子參數或欄位上的 @BindParam
註解支援自訂名稱對應 (如果存在)。如有必要,您也可以在 DataBinder
上組態 NameResolver
以自訂要使用的引數名稱。
型別轉換 會根據需要應用於轉換使用者輸入。如果建構子參數是一個物件,則會以相同方式遞迴建構它,但透過巢狀屬性路徑。這表示建構子綁定會建立目標物件及其包含的任何物件。
建構子綁定支援 List
、Map
和陣列引數,這些引數可以從單一字串轉換而來 (例如,逗號分隔列表),或基於索引鍵 (例如 accounts[2].name
或 account[KEY].name
)。
綁定和轉換錯誤會反映在 DataBinder
的 BindingResult
中。如果目標物件成功建立,則在呼叫 construct
後,target
會設定為已建立的實例。
使用 BeanWrapper
進行屬性綁定
org.springframework.beans
套件遵循 JavaBeans 標準。JavaBean 是一個具有預設無引數建構子的類別,並遵循命名慣例,其中 (例如) 名為 bingoMadness
的屬性將具有 setter 方法 setBingoMadness(..)
和 getter 方法 getBingoMadness()
。有關 JavaBeans 和規格的更多資訊,請參閱 javabeans。
beans 套件中一個非常重要的類別是 BeanWrapper
介面及其對應的實作 (BeanWrapperImpl
)。正如從 javadoc 中引用的,BeanWrapper
提供設定和取得屬性值 (個別或批量)、取得屬性描述器以及查詢屬性以確定它們是否可讀寫的功能。此外,BeanWrapper
提供對巢狀屬性的支援,允許將子屬性的屬性設定到無限深度。BeanWrapper
還支援新增標準 JavaBeans PropertyChangeListeners
和 VetoableChangeListeners
的能力,而無需目標類別中的支援程式碼。最後但並非最不重要的一點是,BeanWrapper
提供對設定索引屬性的支援。BeanWrapper
通常不直接由應用程式碼使用,而是由 DataBinder
和 BeanFactory
使用。
BeanWrapper
的工作方式部分由其名稱表示:它包裝一個 bean 以對該 bean 執行操作,例如設定和擷取屬性。
設定和取得基本和巢狀屬性
設定和取得屬性是透過 BeanWrapper
的 setPropertyValue
和 getPropertyValue
多載方法變體完成的。有關詳細資訊,請參閱其 Javadoc。下表顯示了這些慣例的一些範例
運算式 | 說明 |
---|---|
|
指示與 |
|
指示屬性 |
|
指示索引屬性 |
|
指示由 |
(如果您不打算直接使用 BeanWrapper
,則下一個章節對您而言並非至關重要。如果您僅使用 DataBinder
和 BeanFactory
及其預設實作,則應跳到關於 PropertyEditors
的章節。)
以下兩個範例類別使用 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
's
Spring 使用 PropertyEditor
的概念來實現 Object
和 String
之間的轉換。以不同於物件本身的方式表示屬性可能很方便。例如,Date
可以以人類可讀的方式表示 (如 String
: '2007-14-09'
),同時我們仍然可以將人類可讀的形式轉換回原始日期 (或者,更好的是,將以人類可讀形式輸入的任何日期轉換回 Date
物件)。此行為可以透過註冊 java.beans.PropertyEditor
型別的自訂編輯器來實現。在 BeanWrapper
上或在特定的 IoC 容器中 (如前一章所述) 註冊自訂編輯器,使其了解如何將屬性轉換為所需的型別。有關 PropertyEditor
的更多資訊,請參閱 Oracle 提供的 java.beans
套件的 javadoc。
以下是一些在 Spring 中使用屬性編輯的範例
-
Bean 上的屬性設定是透過使用
PropertyEditor
實作完成的。當您使用String
作為在 XML 檔案中宣告的某些 bean 的屬性值時,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
型別屬性的 PropertyEditor
。
com chank pop Something SomethingEditor // the PropertyEditor for the Something class
請注意,您也可以在此處使用標準 BeanInfo
JavaBeans 機制 (在 此處 有一定程度的描述)。以下範例使用 BeanInfo
機制來明確註冊與關聯類別的屬性的一個或多個 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
's
當將 bean 屬性設定為字串值時,Spring IoC 容器最終會使用標準 JavaBeans PropertyEditor
實作將這些字串轉換為屬性的複雜型別。Spring 預先註冊了許多自訂 PropertyEditor
實作 (例如,將表示類別名稱的字串轉換為 Class
物件)。此外,Java 的標準 JavaBeans PropertyEditor
查找機制允許將類別的 PropertyEditor
適當地命名並放置在與它提供支援的類別相同的套件中,以便可以自動找到它。
如果需要註冊其他自訂 PropertyEditors
,則可以使用多種機制。最手動的方法 (通常不方便或不建議使用) 是使用 ConfigurableBeanFactory
介面的 registerCustomEditor()
方法,前提是您具有 BeanFactory
參考。另一種 (稍微方便一點的) 機制是使用名為 CustomEditorConfigurer
的特殊 bean 工廠後處理器。雖然您可以將 bean 工廠後處理器與 BeanFactory
實作搭配使用,但 CustomEditorConfigurer
具有巢狀屬性設定,因此我們強烈建議您將其與 ApplicationContext
搭配使用,您可以在其中以與任何其他 bean 類似的方式部署它,並且可以自動偵測和應用它。
請注意,所有 bean 工廠和應用程式 Context 都會透過使用 BeanWrapper
來處理屬性轉換,從而自動使用許多內建的屬性編輯器。BeanWrapper
註冊的標準屬性編輯器列在前一節中。此外,ApplicationContext
也會覆寫或新增其他編輯器,以適當於特定應用程式 Context 型別的方式處理資源查找。
標準 JavaBeans PropertyEditor
實例用於將表示為字串的屬性值轉換為屬性的實際複雜型別。您可以使用 bean 工廠後處理器 CustomEditorConfigurer
,方便地為 ApplicationContext
新增對其他 PropertyEditor
實例的支援。
考慮以下範例,其中定義了一個名為 ExoticType
的使用者類別,以及另一個名為 DependsOnExoticType
的類別,後者需要將 ExoticType
設為屬性。
-
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
向 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 容器註冊屬性編輯器 (property editor) 的機制是建立並使用 PropertyEditorRegistrar
。當您需要在多種不同情況下使用相同的屬性編輯器集合時,此介面特別有用。您可以編寫一個對應的 registrar 并在每種情況下重複使用它。PropertyEditorRegistrar
實例與一個名為 PropertyEditorRegistry
的介面協同工作,該介面由 Spring 的 BeanWrapper
(和 DataBinder
) 實作。當與 CustomEditorConfigurer
(在此處描述) 結合使用時,PropertyEditorRegistrar
實例尤其方便,CustomEditorConfigurer
暴露了一個名為 setPropertyEditorRegistrars(..)
的屬性。以這種方式添加到 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...
}
}
另請參閱 org.springframework.beans.support.ResourceEditorRegistrar
以取得 PropertyEditorRegistrar
實作範例。請注意在其 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
方法的實作只有一行程式碼),並允許將通用的 PropertyEditor
註冊程式碼封裝在一個類別中,然後在需要的眾多控制器之間共享。