此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
数据绑定
数据绑定对于将用户输入绑定到目标对象非常有用,其中用户输入是映射将属性路径作为键,遵循 JavaBeans 约定。DataBinder
是支持该作的主类,它提供了两种绑定用户的方式 输入:
可以同时应用构造函数和属性绑定,也可以仅应用一个。
构造函数绑定
要使用构造函数绑定,请执行以下作:
-
创建一个
DataBinder
跟null
作为目标对象。 -
设置
targetType
到目标类。 -
叫
construct
.
目标类应该有一个公共构造函数或一个非公共构造函数带有参数。如果有多个构造函数,则默认构造函数(如果存在)被使用。
默认情况下,参数值是通过构造函数参数名称查找的。Spring MVC 和WebFlux 支持通过@BindParam
构造函数上的注释参数或字段(如果存在)。如有必要,您还可以配置NameResolver
上DataBinder
自定义要使用的参数名称。
根据需要应用类型转换来转换用户输入。如果构造函数参数是对象,则以相同的方式递归构造方式,但通过嵌套属性路径。这意味着构造函数绑定会同时创建目标对象及其包含的任何对象。
构造函数绑定支持List
,Map
,数组参数要么从单个字符串(例如逗号分隔列表)转换,要么基于索引键,例如accounts[2].name
或account[KEY].name
.
绑定和转换错误反映在BindingResult
的DataBinder
. 如果成功创建了目标,则target
设置为创建的实例在调用construct
.
属性绑定BeanWrapper
这org.springframework.beans
包遵循 JavaBeans 标准。JavaBean 是一个具有默认无参数构造函数的类,它遵循命名约定,其中(例如)名为bingoMadness
愿意 有一个 setter 方法setBingoMadness(..)
和一个 getter 方法getBingoMadness()
. 为 有关 JavaBeans 和规范的更多信息,请参阅 Javabeans。
bean 包中一个非常重要的类是BeanWrapper
接口及其相应的实现 (BeanWrapperImpl
). 正如 javadoc 中引用的那样,BeanWrapper
提供了设置和获取属性值(单独或批量)、获取属性描述符和查询属性以确定它们是否可读或可写的功能。此外,BeanWrapper
提供对嵌套属性的支持,启用子属性上的属性设置到无限深度。 这BeanWrapper
还支持添加标准 JavaBeans 的功能PropertyChangeListeners
和VetoableChangeListeners
,无需在目标类中支持代码。最后但并非最不重要的一点是,BeanWrapper
支持设置索引属性。 这BeanWrapper
通常不直接由应用程序代码使用,而是由DataBinder
和BeanFactory
.
方式BeanWrapper
works 部分由其名称表示:它包装一个 bean 以对该 bean 执行作,例如设置和检索属性。
设置和获取基本属性和嵌套属性
设置和获取属性是通过setPropertyValue
和getPropertyValue
重载方法变体BeanWrapper
.请参阅他们的 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
s 和Employee
s:
-
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
看的 javadocjava.beans
来自 Oracle 的包.
在 Spring 中使用属性编辑的几个示例:
-
在 Bean 上设置属性是通过使用
PropertyEditor
实现。 当您使用String
作为您声明的某个 bean 的属性的值 在 XML 文件中,Spring(如果相应属性的 setter 具有Class
参数)使用ClassEditor
尝试将参数解析为Class
对象。 -
在 Spring 的 MVC 框架中解析 HTTP 请求参数是通过使用各种类型来完成的 之
PropertyEditor
可以在CommandController
.
Spring 内置了许多PropertyEditor
实施,让生活变得轻松。
它们都位于org.springframework.beans.propertyeditors
包。默认情况下,大多数(但不是全部,如下表所示)由BeanWrapperImpl
.如果属性编辑器可以以某种方式进行配置,您可以
仍然注册您自己的变体以覆盖默认变体。下表描述了
各种PropertyEditor
Spring 提供的实现:
类 | 解释 |
---|---|
|
字节数组的编辑器。将字符串转换为其相应的字节
交涉。默认注册者 |
|
将类表示为实际类的字符串,反之亦然。当
class 未找到,则 |
|
可自定义的属性编辑器 |
|
集合的属性编辑器,转换任何源 |
|
可自定义的属性编辑器 |
|
适用于任何的可定制属性编辑器 |
|
将字符串解析为 |
|
单向属性编辑器,可以接受字符串并生成(通过 中间 |
|
可以将字符串解析为 |
|
可以将字符串解析为 |
|
可以转换字符串(格式化为 javadoc 中定义的格式 |
|
修剪字符串的属性编辑器。(可选)允许将空字符串转换为 |
|
可以将 URL 的字符串表示形式解析为实际的 |
Spring 使用java.beans.PropertyEditorManager
设置属性的搜索路径
可能需要的编辑。搜索路径还包括sun.bean.editors
哪
包括PropertyEditor
诸如Font
,Color
,以及大部分
原始类型。另请注意,标准 JavaBeans 基础架构
自动发现PropertyEditor
类(无需注册它们
显式),如果它们与它们处理的类位于同一包中并且具有相同的
name 作为该类,并带有Editor
附加。例如,可以有以下内容
类和包结构,这对于SomethingEditor
类
识别并用作PropertyEditor
为Something
-typed 属性。
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
以下 Java 源代码用于引用SomethingBeanInfo
类 员工 aCustomNumberEditor
使用age
属性的Something
类:
-
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 容器最终使用标准 JavaBeansPropertyEditor
实现将这些字符串转换为 财产。 Spring 预注册了许多自定义PropertyEditor
实现(例如,将将以字符串表示的类名转换为Class
对象)。 此外 Java 的标准 JavaBeansPropertyEditor
查找机制允许PropertyEditor
对于一个类,应该适当地命名并放置在与它提供支持的类相同的包中,以便可以自动找到它。
如果需要注册其他自定义PropertyEditors
,有几种机制 可用。 最手动的方法,通常不方便或推荐,是使用registerCustomEditor()
方法ConfigurableBeanFactory
接口,假设你有一个BeanFactory
参考。 另一种(稍微方便一点)机制是使用特殊的豆工厂称为后处理器CustomEditorConfigurer
. 虽然您可以使用 bean 工厂后处理器 跟BeanFactory
实现,则CustomEditorConfigurer
有一个nested 属性设置,因此我们强烈建议您将其与ApplicationContext
,您可以在其中以与任何其他 bean 类似的方式部署它,并且可以自动检测和应用它。
请注意,所有 bean 工厂和应用程序上下文都会自动使用许多内置属性编辑器,通过使用BeanWrapper
自 处理属性转换。标准属性编辑器BeanWrapper
寄存器在上一节中列出。 此外ApplicationContext
还会覆盖或添加其他编辑器来处理资源查找,以适合特定应用程序上下文类型的方式。
标准 JavaBeansPropertyEditor
实例用于将属性值表示为字符串,转换为属性的实际复杂类型。您可以使用CustomEditorConfigurer
,一个豆厂后处理器,方便添加支持额外的PropertyEditor
instances 到ApplicationContext
.
考虑以下示例,该示例定义了一个名为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
}
当事情设置正确后,我们希望能够将类型属性赋值为string,其中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
,然后它将能够根据需要使用它:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
</map>
</property>
</bean>
PropertyEditorRegistrar
向 Spring 容器注册属性编辑器的另一种机制是
创建并使用PropertyEditorRegistrar
.此界面在以下情况下特别有用
您需要在几种不同的情况下使用同一组属性编辑器。
您可以编写相应的注册商并在每种情况下重复使用它。PropertyEditorRegistrar
实例与名为PropertyEditorRegistry
,一个由 Spring 实现的接口BeanWrapper
(和DataBinder
).PropertyEditorRegistrar
实例特别方便
当与CustomEditorConfigurer
(此处所述),它公开了一个属性
叫setPropertyEditorRegistrars(..)
.PropertyEditorRegistrar
已添加实例
设置为CustomEditorConfigurer
以这种方式可以轻松分享DataBinder
和 Spring MVC 控制器。此外,它避免了对自定义编辑器进行同步的需要:一个PropertyEditorRegistrar
有望创造新鲜感PropertyEditor
每个 Bean 创建尝试的实例。
以下示例演示如何创建自己的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 框架,使用PropertyEditorRegistrar
在 与数据绑定 Web 控制器结合使用会非常方便。以下示例使用PropertyEditorRegistrar
在实施@InitBinder
方法:
-
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
注册码封装在一个类中,然后在尽可能多的控制器之间共享根据需要。