数据绑定对于将用户输入绑定到用户输入为映射的目标对象非常有用 将属性路径作为键,遵循 JavaBeans 约定。 是支持此功能的主类,它提供了两种绑定用户的方式 输入:DataBinder

  • 构造函数绑定 - 将用户输入绑定到公共数据 构造函数,在用户输入中查找构造函数参数值。

  • 属性绑定 - 将用户输入绑定到设置器,匹配来自 用户对目标对象结构属性的输入。

可以同时应用构造函数和属性绑定,也可以仅应用一个。

构造函数绑定

要使用构造函数绑定,请执行以下操作:

  1. 创建一个 with 作为目标对象。DataBindernull

  2. 设置为目标类。targetType

  3. 叫。construct

目标类应具有单个公共构造函数或单个非公共构造函数 有参数。如果有多个构造函数,则默认构造函数(如果存在) 被使用。

默认情况下,构造函数参数名称用于查找参数值,但您可以 配置 .Spring MVC 和 WebFlux 都依赖于允许自定义名称 的值,通过构造函数参数上的注释进行绑定。NameResolver@BindParam

根据需要应用类型转换来转换用户输入。 如果构造函数参数是一个对象,则在相同的 方式,但通过嵌套属性路径。这意味着构造函数绑定会同时创建两者 目标对象及其包含的任何对象。

绑定和转换错误反映在 . 如果成功创建目标,则设置为创建的实例 在调用 .BindingResultDataBindertargetconstruct

属性绑定BeanWrapper

该软件包遵循 JavaBeans 标准。 JavaBean 是一个具有默认无参数构造函数的类,其内容如下 一种命名约定,其中(例如)名为 有一个 setter 方法和一个 getter 方法。为 有关 JavaBeans 和规范的更多信息,请参阅 JavaBeansorg.springframework.beansbingoMadnesssetBingoMadness(..)getBingoMadness()

beans 软件包中一个非常重要的类是接口及其 相应的实现()。正如引用自 javadoc 的那样,它提供了设置和获取属性值的功能(单独或在 bulk),获取属性描述符,并查询属性以确定它们是否是 可读或可写。此外,还提供对嵌套属性的支持, 将子属性的属性设置为无限深度。它还支持添加标准 JavaBeans 和 的功能,而无需在目标类中支持代码。 最后但并非最不重要的一点是,它提供了对设置索引属性的支持。 通常不由应用程序代码直接使用,而由 和 使用 。BeanWrapperBeanWrapperImplBeanWrapperBeanWrapperBeanWrapperPropertyChangeListenersVetoableChangeListenersBeanWrapperBeanWrapperDataBinderBeanFactory

作品的方式部分由它的名字表示:它包裹着豆子 对该 Bean 执行操作,例如设置和检索属性。BeanWrapper

设置和获取基本属性和嵌套属性

设置和获取属性是通过 的 和 重载方法变体完成的。查看他们的 Javadoc 详。下表显示了这些约定的一些示例:setPropertyValuegetPropertyValueBeanWrapper

表 1.属性示例
表达 解释

name

指示对应于 or 和 方法的属性。namegetName()isName()setName(..)

account.name

指示对应于 (例如)OR 方法。nameaccountgetAccount().setName()getAccount().getName()

account[2]

指示 indexed 属性的第三个元素。索引属性 可以是 、 或其他自然有序的集合。accountarraylist

account[COMPANYNAME]

指示由属性键索引的映射条目的值。COMPANYNAMEaccountMap

(如果您不打算使用 直接的。如果仅使用 和 和 及其默认实现,则应跳到 PropertyEditors 部分BeanWrapperDataBinderBeanFactory

以下两个示例类使用 to get 和 set 性能: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
}

以下代码片段显示了如何检索和操作某些 实例化 S 和 S 的属性:CompanyEmployee

  • 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 使用 a 的概念来实现 an 和 a 之间的转换。它可以很方便 以不同于对象本身的方式表示属性。例如,a 可以以人类可读的方式表示(如 : ),而 我们仍然可以将人类可读的形式转换回原始日期(或者,甚至 更好的是,将以人类可读形式输入的任何日期转换回对象)。这 可以通过注册 类型的自定义编辑器来实现行为。在 或 上注册自定义编辑器, 或者,在特定的 IoC 容器中(如上一章所述),给它 了解如何将属性转换为所需类型。有关 的更多信息,请参见 Oracle 提供的 java.beans 包的 javadocPropertyEditorObjectStringDateString'2007-14-09'Datejava.beans.PropertyEditorBeanWrapperPropertyEditor

在 Spring 中使用属性编辑的几个示例:

  • 在 Bean 上设置属性是通过使用实现完成的。 当您使用作为声明的某个 Bean 的属性的值时 在 XML 文件中,Spring(如果相应属性的 setter 具有参数)用于尝试将参数解析为对象。PropertyEditorStringClassClassEditorClass

  • 在 Spring 的 MVC 框架中解析 HTTP 请求参数是通过使用各种方式完成的 的实现,您可以在 . 的所有子类中手动绑定。PropertyEditorCommandController

Spring 有许多内置的实现,让生活变得轻松。 它们都位于包裹中。默认情况下,大多数(但不是全部,如下表所示)都由 注册。如果属性编辑器可以以某种方式进行配置,则可以 仍然注册您自己的变体以覆盖默认变体。下表描述了 Spring 提供的各种实现:PropertyEditororg.springframework.beans.propertyeditorsBeanWrapperImplPropertyEditor

表 2.内置实现PropertyEditor
解释

ByteArrayPropertyEditor

字节数组的编辑器。将字符串转换为其对应的字节 交涉。默认注册时为 。BeanWrapperImpl

ClassEditor

分析表示类到实际类的字符串,反之亦然。当一个 找不到类,抛出一个。默认情况下,由 注册。IllegalArgumentExceptionBeanWrapperImpl

CustomBooleanEditor

属性的可自定义属性编辑器。默认情况下,注册者注册,但可以通过将其的自定义实例注册为 自定义编辑器。BooleanBeanWrapperImpl

CustomCollectionEditor

集合的属性编辑器,将任何源转换为给定的目标类型。CollectionCollection

CustomDateEditor

可自定义的属性编辑器,支持自定义 .不 默认注册。必须根据需要使用适当的格式进行用户注册。java.util.DateDateFormat

CustomNumberEditor

任何子类(如 、 、 或 .默认情况下,注册者注册但可被覆盖 将其的自定义实例注册为自定义编辑器。NumberIntegerLongFloatDoubleBeanWrapperImpl

FileEditor

将字符串解析为对象。默认情况下,由 注册。java.io.FileBeanWrapperImpl

InputStreamEditor

单向属性编辑器,可以获取字符串并生成(通过 intermediate 和 ) an,以便可以直接将属性设置为字符串。请注意,默认用法不会关闭 给你。默认情况下,由 注册。ResourceEditorResourceInputStreamInputStreamInputStreamBeanWrapperImpl

LocaleEditor

可以将字符串解析为对象,反之亦然(字符串格式为 ,与 的方法相同)。也接受空格作为分隔符,作为下划线的替代方法。 默认情况下,由 注册。Locale[language]_[country]_[variant]toString()LocaleBeanWrapperImpl

PatternEditor

可以将字符串解析为对象,反之亦然。java.util.regex.Pattern

PropertiesEditor

可以将字符串(使用类的 javadoc 中定义的格式进行格式化)转换为对象。默认情况下,已注册 由。java.util.PropertiesPropertiesBeanWrapperImpl

StringTrimmerEditor

修剪字符串的属性编辑器。(可选)允许转换空字符串 转换为值。默认情况下未注册 — 必须由用户注册。null

URLEditor

可以将 URL 的字符串表示形式解析为实际对象。 默认情况下,由 注册。URLBeanWrapperImpl

Spring 使用 来设置属性的搜索路径 可能需要的编辑器。搜索路径还包括 ,其中 包括 、 和大多数 基元类型。另请注意,标准的 JavaBeans 基础结构 自动发现类(无需注册 显式地),如果它们与它们处理的类位于同一包中并且具有相同的 name 作为该类,并附加。例如,可以有以下内容 类和包结构,这足以使类 识别并用作 for -type 属性。java.beans.PropertyEditorManagersun.bean.editorsPropertyEditorFontColorPropertyEditorEditorSomethingEditorPropertyEditorSomething

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

请注意,您也可以在此处使用标准的 JavaBeans 机制 (在这里进行了某种程度的描述)。这 下面的示例使用该机制将一个或多个实例显式注册到关联类的属性:BeanInfoBeanInfoPropertyEditor

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

引用类的以下 Java 源代码 将 A 与类的属性相关联:SomethingBeanInfoCustomNumberEditorageSomething

  • 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 实现,将这些字符串转换为 财产。Spring 预注册了许多自定义实现(例如,到 将表示为字符串的类名转换为对象)。此外 Java 的标准 JavaBeans 查找机制允许对类进行适当的命名,并将其放置在与类相同的包中 它为此提供支持,以便可以自动找到它。PropertyEditorPropertyEditorClassPropertyEditorPropertyEditor

如果需要注册其他自定义,有几种机制是 可用。最手动的方法,通常不方便或 推荐使用接口的方法,假设你有一个引用。 另一种(稍微方便一点)的机制是使用特殊的豆子工厂 后处理器称为 .虽然您可以使用 bean factory 后处理器 在实现中,有一个 嵌套属性设置,因此我们强烈建议您将其与 一起使用,您可以在其中以与任何其他 Bean 类似的方式部署它,并且 可以自动检测和应用的地方。PropertyEditorsregisterCustomEditor()ConfigurableBeanFactoryBeanFactoryCustomEditorConfigurerBeanFactoryCustomEditorConfigurerApplicationContext

请注意,所有 Bean 工厂和应用程序上下文都会自动使用许多 内置属性编辑器,通过使用 处理属性转换。上一节中列出了寄存器的标准属性编辑器。 此外,s 还会覆盖或添加其他编辑器来处理 以适合特定应用程序上下文类型的方式进行资源查找。BeanWrapperBeanWrapperApplicationContext

标准 JavaBeans 实例用于转换属性值 表示为属性的实际复杂类型的字符串。可以使用 bean factory 后处理器,方便地添加 支持对 .PropertyEditorCustomEditorConfigurerPropertyEditorApplicationContext

请考虑以下示例,该示例定义了一个名为 和 的用户类 另一个名为 的类需要设置为一个属性:ExoticTypeDependsOnExoticTypeExoticType

  • 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 属性赋值为 字符串,将其转换为实际实例。以下 Bean 定义显示了如何设置此关系:PropertyEditorExoticType

<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())
	}
}

最后,以下示例演示如何使用 向 注册 new ,然后 可以根据需要使用它:CustomEditorConfigurerPropertyEditorApplicationContext

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

PropertyEditorRegistrar

向 Spring 容器注册属性编辑器的另一种机制是 创建并使用 .此界面在以下情况下特别有用 您需要在几种不同情况下使用同一组属性编辑器。 您可以编写相应的注册器,并在每种情况下重复使用它。 实例与一个名为 的接口协同工作,该接口由 Spring(和 )实现。 实例特别方便 当与(此处描述)结合使用时,它会公开属性 叫。 添加的实例 以这种方式可以很容易地与 和 共享 Spring MVC 控制器。此外,它避免了在自定义上同步的需要 editors:A 应该为每次 Bean 创建尝试创建新的实例。PropertyEditorRegistrarPropertyEditorRegistrarPropertyEditorRegistryBeanWrapperDataBinderPropertyEditorRegistrarCustomEditorConfigurersetPropertyEditorRegistrars(..)PropertyEditorRegistrarCustomEditorConfigurerDataBinderPropertyEditorRegistrarPropertyEditor

以下示例演示如何创建自己的实现: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.ResourceEditorRegistrarPropertyEditorRegistrarregisterCustomEditors(..)

下一个示例演示如何配置和注入实例 我们的进入:CustomEditorConfigurerCustomPropertyEditorRegistrar

<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 控制器结合使用非常方便。以下 示例在方法的实现中使用 A:PropertyEditorRegistrarPropertyEditorRegistrar@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@InitBinderPropertyEditor