数据绑定
数据绑定对于将用户输入绑定到目标对象非常有用,其中用户输入是键为属性路径的映射,遵循 JavaBeans 约定。
DataBinder 是支持此功能的主要类,它提供了两种绑定用户输入的方法:
您可以同时应用构造函数绑定和属性绑定,也可以只应用其中一种。
构造函数绑定
要使用构造函数绑定:
-
创建一个
DataBinder,目标对象为null。 -
将
targetType设置为目标类。 -
调用
construct。
目标类应该有一个单一的公共构造函数或一个带参数的单一非公共构造函数。如果有多个构造函数,则使用默认构造函数(如果存在)。
默认情况下,构造函数参数名称用于查找参数值,但您可以配置 NameResolver。Web MVC 和 WebFlux 都依赖于通过构造函数参数上的 @BindParam 注解来自定义绑定值的名称。
类型转换 会根据需要应用于转换用户输入。 如果构造函数参数是一个对象,它将以相同的方式递归构造,但通过嵌套属性路径。这意味着构造函数绑定既创建目标对象,也创建它包含的任何对象。
绑定和转换错误反映在 DataBinder 的 BindingResult 中。
如果目标创建成功,则在调用 construct 后,target 将设置为创建的实例。
使用 BeanWrapper 进行属性绑定
infra.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
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;
}
}
-
Java
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;
}
}
以下代码片段显示了如何检索和操作实例化的 Company 和 Employee 的一些属性的示例:
-
Java
BeanWrapper company = new BeanWrapperImpl(new Company());
// 设置公司名称..
company.setPropertyValue("name", "Some Company Inc.");
// ... 也可以这样完成:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);
// 好的,让我们创建董事并将其绑定到公司:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());
// 通过公司检索 managingDirector 的薪水
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
PropertyEditor
Infra 使用 PropertyEditor 的概念来实现 Object 和 String 之间的转换。以不同于对象本身的方式表示属性可能很方便。例如,Date 可以以人类可读的方式表示(如 String: '2007-14-09'),而我们仍然可以将人类可读的形式转换回原始日期(或者更好的是,将以人类可读形式输入的任何日期转换回 Date 对象)。这种行为可以通过注册类型为 java.beans.PropertyEditor 的自定义编辑器来实现。在 BeanWrapper 上注册自定义编辑器,或者在特定的 IoC 容器中注册(如前一章所述),使其了解如何将属性转换为所需的类型。有关 PropertyEditor 的更多信息,请参阅 来自 Oracle 的 java.beans 包的 javadoc。
Infra 中使用属性编辑的几个示例:
-
在 bean 上设置属性是通过使用
PropertyEditor实现来完成的。当您使用String作为 XML 文件中声明的某个 bean 的属性值时,Infra(如果相应属性的 setter 具有Class参数)使用ClassEditor尝试将参数解析为Class对象。 -
解析 Infra MVC 框架中的 HTTP 请求参数是通过使用各种
PropertyEditor实现来完成的,您可以手动将这些实现绑定在CommandController的所有子类中。
Infra 有许多内置的 PropertyEditor 实现,使生活变得轻松。它们都位于 infra.beans.propertyeditors 包中。大多数(但不是全部,如下表所示)默认由 BeanWrapperImpl 注册。如果属性编辑器以某种方式可配置,您仍然可以注册自己的变体以覆盖默认变体。下表描述了 Infra 提供的各种 PropertyEditor 实现:
| 类 | 说明 |
|---|---|
|
字节数组编辑器。将字符串转换为其对应的字节表示形式。默认由 |
|
将表示类的字符串解析为实际类,反之亦然。未找到类时,抛出 |
|
|
|
集合的属性编辑器,将任何源 |
|
|
|
任何 |
|
将字符串解析为 |
|
单向属性编辑器,可以获取字符串并生成(通过中间 |
|
可以将字符串解析为 |
|
可以将字符串解析为 |
|
可以将字符串(使用 |
|
修剪字符串的属性编辑器。可选择允许将空字符串转换为 |
|
可以将 URL 的字符串表示形式解析为实际的 |
Infra 使用 java.beans.PropertyEditorManager 设置可能需要的属性编辑器的搜索路径。搜索路径还包括 sun.bean.editors,其中包括针对诸如 Font、Color 和大多数原始类型等类型的 PropertyEditor 实现。另请注意,标准 JavaBeans 基础设施会自动发现 PropertyEditor 类(无需您显式注册它们),只要它们与它们处理的类在同一个包中,并且具有与该类相同的名称,并附加了 Editor。例如,可以有以下类和包结构,这足以让 SomethingEditor 类被识别并用作 Something 类型属性的 PropertyEditor。
com
chank
pop
Something
SomethingEditor // Something 类的 PropertyEditor
请注意,您也可以在此处使用标准 BeanInfo JavaBeans 机制(在一定程度上描述于 此处)。以下示例使用 BeanInfo 机制显式地向关联类的属性注册一个或多个 PropertyEditor 实例:
com
chank
pop
Something
SomethingBeanInfo // Something 类的 BeanInfo
以下引用的 SomethingBeanInfo 类的 Java 源代码将 CustomNumberEditor 与 Something 类的 age 属性关联起来:
-
Java
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());
}
}
}
自定义 PropertyEditor
当将 bean 属性设置为字符串值时,Infra IoC 容器最终使用标准 JavaBeans PropertyEditor 实现将这些字符串转换为属性的复杂类型。Infra 预注册了许多自定义 PropertyEditor 实现(例如,将表示为字符串的类名转换为 Class 对象)。此外,Java 的标准 JavaBeans PropertyEditor 查找机制允许对类的 PropertyEditor 进行适当命名并将其放置在与其提供支持的类相同的包中,以便可以自动找到它。
如果需要注册其他自定义 PropertyEditors,有几种机制可用。最手动的如果不通常方便或不推荐的方法是使用 ConfigurableBeanFactory 接口的 registerCustomEditor() 方法,假设您有 BeanFactory 引用。另一种(稍微方便一点的)机制是使用名为 CustomEditorConfigurer 的特殊 bean 工厂后处理器。虽然您可以将 bean 工厂后处理器与 BeanFactory 实现一起使用,但 CustomEditorConfigurer 具有嵌套属性设置,因此我们强烈建议您将其与 ApplicationContext 一起使用,在 ApplicationContext 中,您可以以类似于任何其他 bean 的方式部署它,并且可以自动检测并应用它。
请注意,所有 bean 工厂和应用程序上下文都会自动使用许多内置属性编辑器,通过使用 BeanWrapper 来处理属性转换。BeanWrapper 注册的标准属性编辑器列在 上一节 中。此外,ApplicationContext 还会覆盖或添加其他编辑器,以适合特定应用程序上下文类型的方式处理资源查找。
标准 JavaBeans PropertyEditor 实例用于将表示为字符串的属性值转换为属性的实际复杂类型。您可以使用 CustomEditorConfigurer(一个 bean 工厂后处理器)方便地向 ApplicationContext 添加对其他 PropertyEditor 实例的支持。
考虑以下示例,该示例定义了一个名为 ExoticType 的用户类和另一个名为 DependsOnExoticType 的类,该类需要将 ExoticType 设置为属性:
-
Java
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;
}
}
当一切设置正确时,我们希望能够将 type 属性分配为字符串,PropertyEditor 会将其转换为实际的 ExoticType 实例。以下 bean 定义显示了如何设置此关系:
<bean id="sample" class="example.DependsOnExoticType">
<property name="type" value="aNameForExoticType"/>
</bean>
PropertyEditor 实现可能类似于以下内容:
-
Java
package example;
import java.beans.PropertyEditorSupport;
// 将字符串表示形式转换为 ExoticType 对象
public class ExoticTypeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
setValue(new ExoticType(text.toUpperCase()));
}
}
最后,以下示例显示了如何使用 CustomEditorConfigurer 向 ApplicationContext 注册新的 PropertyEditor,然后 ApplicationContext 将能够根据需要使用它:
<bean class="infra.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
</map>
</property>
</bean>
PropertyEditorRegistrar
向 Infra 容器注册属性编辑器的另一种机制是创建并使用 PropertyEditorRegistrar。当您需要在几种不同情况下使用同一组属性编辑器时,此接口特别有用。您可以编写相应的注册器并在每种情况下重用它。PropertyEditorRegistrar 实例与名为 PropertyEditorRegistry 的接口一起工作,该接口由 Infra BeanWrapper(和 DataBinder)实现。当与 CustomEditorConfigurer(此处 描述)结合使用时,PropertyEditorRegistrar 实例特别方便,它公开了一个名为 setPropertyEditorRegistrars(..) 的属性。以这种方式添加到 CustomEditorConfigurer 的 PropertyEditorRegistrar 实例可以轻松地与 DataBinder 和 Web MVC 控制器共享。此外,它避免了对自定义编辑器进行同步的需要:预计 PropertyEditorRegistrar 会为每次 bean 创建尝试创建新的 PropertyEditor 实例。
以下示例显示了如何创建自己的 PropertyEditorRegistrar 实现:
-
Java
package com.foo.editors.spring;
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
// 预计会创建新的 PropertyEditor 实例
registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
// 您可以在此处注册所需的任意数量的自定义属性编辑器...
}
}
另请参阅 infra.beans.support.ResourceEditorRegistrar 以获取示例 PropertyEditorRegistrar 实现。请注意在其 registerCustomEditors(..) 方法的实现中,它是如何创建每个属性编辑器的新实例的。
下一个示例显示了如何配置 CustomEditorConfigurer 并将我们的 CustomPropertyEditorRegistrar 实例注入其中:
<bean class="infra.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="customPropertyEditorRegistrar"/>
</list>
</property>
</bean>
<bean id="customPropertyEditorRegistrar"
class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>
最后(有点偏离本章的重点),对于使用 Infra MVC web 框架 的用户,将 PropertyEditorRegistrar 与数据绑定 Web 控制器结合使用非常方便。以下示例在 @InitBinder 方法的实现中使用了 PropertyEditorRegistrar:
-
Java
@Controller
public class RegisterUserController {
private final PropertyEditorRegistrar customPropertyEditorRegistrar;
RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
this.customPropertyEditorRegistrar = propertyEditorRegistrar;
}
@InitBinder
void initBinder(WebDataBinder binder) {
this.customPropertyEditorRegistrar.registerCustomEditors(binder);
}
// 与注册用户相关的其他方法
}
这种 PropertyEditor 注册风格可以产生简洁的代码(@InitBinder 方法的实现只有一行),并允许将常见的 PropertyEditor 注册代码封装在一个类中,然后在需要的任意数量的控制器之间共享。