对于最新稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

Bean 操作与 BeanWrapper

org.springframework.beans 包遵循 JavaBeans 标准。 JavaBean 是一个具有默认无参构造函数的类,并且遵循一种命名约定:例如,名为 bingoMadness 的属性 应具有 setter 方法 setBingoMadness(..) 和 getter 方法 getBingoMadness()。有关 JavaBeans 及其规范的更多信息,请参见 javabeansspring-doc.cadn.net.cn

beans 包中一个非常重要的类是 BeanWrapper 接口及其对应的实现类(BeanWrapperImpl)。正如 Javadoc 所述,BeanWrapper 提供了设置和获取属性值(单独或批量)、获取属性描述符以及查询属性以确定其是否可读或可写的功能。此外,BeanWrapper 还支持嵌套属性,允许对子属性进行无限深度的设置。BeanWrapper 还支持添加标准的 JavaBeans PropertyChangeListenersVetoableChangeListeners,而无需在目标类中编写支持代码。最后但同样重要的是,BeanWrapper 提供了对设置索引属性的支持。BeanWrapper 通常不会被应用程序代码直接使用,而是由 DataBinderBeanFactory 使用。spring-doc.cadn.net.cn

BeanWrapper 的工作方式部分体现在其名称中:它包装一个 bean,以便对该 bean 执行操作,例如设置和获取属性。spring-doc.cadn.net.cn

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

设置和获取属性是通过 setPropertyValuegetPropertyValueBeanWrapper 重载方法变体完成的。有关详细信息,请参阅它们的 Javadoc。下表展示了一些这些约定的示例:spring-doc.cadn.net.cn

表1. 属性示例
表达式 说明

namespring-doc.cadn.net.cn

指示与 namegetName() 以及 isName() 方法对应的属性 setName(..)spring-doc.cadn.net.cn

account.namespring-doc.cadn.net.cn

表示属性 name 的嵌套属性 account,该嵌套属性对应于(例如)getAccount().setName()getAccount().getName() 方法。spring-doc.cadn.net.cn

account[2]spring-doc.cadn.net.cn

表示索引属性 account第三个元素。索引属性可以是 arraylist 或其他自然有序的集合类型。spring-doc.cadn.net.cn

account[COMPANYNAME]spring-doc.cadn.net.cn

表示 COMPANYNAME account 属性中以 Map 键索引的映射项的值。spring-doc.cadn.net.cn

(如果您不打算直接使用 BeanWrapper,那么下一节对您来说并不是至关重要。如果您仅使用 DataBinderBeanFactory 及其默认实现,您可以直接跳转到关于 PropertyEditors 的章节。)spring-doc.cadn.net.cn

以下两个示例类使用 BeanWrapper 来获取和设置属性:spring-doc.cadn.net.cn

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

以下代码片段展示了一些如何获取和操作已实例化的 CompanyEmployee 对象属性的示例:spring-doc.cadn.net.cn

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的概念来实现ObjectString之间的转换。有时以不同于对象本身的方式来表示属性会很有用。例如,Date可以以一种人类可读的方式表示(如String'2007-14-09'),同时我们仍然可以将这种人类可读的形式转换回原始日期(或者更好的是,将以任何人类可读形式输入的日期转换回Date对象)。这种行为可以通过注册类型为java.beans.PropertyEditor的自定义编辑器来实现。在BeanWrapper上注册自定义编辑器,或者在特定的 IoC 容器中注册(如前一章所述),可使其具备如何将属性转换为所需类型的知识。有关PropertyEditor的更多信息,请参阅Oracle 提供的java.beans包的 Javadocspring-doc.cadn.net.cn

Spring 中使用属性编辑的几个示例:spring-doc.cadn.net.cn

  • 通过使用 PropertyEditor 的实现类来设置 bean 的属性。 当你在 XML 文件中声明某个 bean 的属性时,若该属性的值为 String 类型, Spring(如果对应属性的 setter 方法具有 Class 类型的参数)会使用 ClassEditor 尝试将该参数解析为一个 Class 对象。spring-doc.cadn.net.cn

  • 在 Spring 的 MVC 框架中,解析 HTTP 请求参数是通过使用各种 PropertyEditor 实现来完成的,你可以手动将这些实现绑定到所有 CommandController 的子类中。spring-doc.cadn.net.cn

Spring 提供了多个内置的 PropertyEditor 实现,以简化开发工作。 它们都位于 org.springframework.beans.propertyeditors 包中。其中大部分(但并非全部,如下表所示)默认情况下会由 BeanWrapperImpl 自动注册。如果某个属性编辑器支持某种形式的配置, 你仍然可以注册自己的变体来覆盖默认实现。下表描述了 Spring 提供的各种 PropertyEditor 实现:spring-doc.cadn.net.cn

表2. 内置的PropertyEditor实现
说明

ByteArrayPropertyEditorspring-doc.cadn.net.cn

字节数组的编辑器。将字符串转换为其对应的字节表示形式。默认由 BeanWrapperImpl 注册。spring-doc.cadn.net.cn

ClassEditorspring-doc.cadn.net.cn

将表示类的字符串解析为实际的类,反之亦然。当找不到某个类时,会抛出 IllegalArgumentException 异常。默认由 BeanWrapperImpl 注册。spring-doc.cadn.net.cn

CustomBooleanEditorspring-doc.cadn.net.cn

用于Boolean属性的可自定义属性编辑器。默认情况下由BeanWrapperImpl注册,但可以通过注册一个自定义实例作为自定义编辑器来覆盖默认行为。spring-doc.cadn.net.cn

CustomCollectionEditorspring-doc.cadn.net.cn

用于集合的属性编辑器,可将任意源 Collection 转换为指定的目标 Collection 类型。spring-doc.cadn.net.cn

CustomDateEditorspring-doc.cadn.net.cn

用于 java.util.Date 的可自定义属性编辑器,支持自定义的 DateFormat。默认情况下未注册,必须由用户根据需要注册并指定适当的格式。spring-doc.cadn.net.cn

CustomNumberEditorspring-doc.cadn.net.cn

适用于任意 Number 子类(例如 IntegerLongFloatDouble)的可自定义属性编辑器。默认情况下由 BeanWrapperImpl 注册,但可以通过注册一个自定义实例作为自定义编辑器来覆盖默认行为。spring-doc.cadn.net.cn

FileEditorspring-doc.cadn.net.cn

将字符串解析为 java.io.File 对象。默认情况下,由 BeanWrapperImpl 注册。spring-doc.cadn.net.cn

InputStreamEditorspring-doc.cadn.net.cn

一种单向属性编辑器,可以接收一个字符串,并通过中间的 ResourceEditorResource 生成一个 InputStream,从而允许将 InputStream 类型的属性直接设置为字符串。请注意,默认用法不会自动关闭该 InputStream。此编辑器默认由 BeanWrapperImpl 注册。spring-doc.cadn.net.cn

LocaleEditorspring-doc.cadn.net.cn

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

PatternEditorspring-doc.cadn.net.cn

可以将字符串解析为 java.util.regex.Pattern 对象,反之亦然。spring-doc.cadn.net.cn

PropertiesEditorspring-doc.cadn.net.cn

可以将字符串(使用 java.util.Properties 类的 Javadoc 中定义的格式进行格式化)转换为 Properties 对象。默认情况下,由 BeanWrapperImpl 注册。spring-doc.cadn.net.cn

StringTrimmerEditorspring-doc.cadn.net.cn

用于修剪字符串的属性编辑器。可选择将空字符串转换为 null 值。默认情况下未注册——必须由用户手动注册。spring-doc.cadn.net.cn

URLEditorspring-doc.cadn.net.cn

可以将 URL 的字符串表示形式解析为实际的 URL 对象。 默认情况下,由 BeanWrapperImpl 注册。spring-doc.cadn.net.cn

Spring 使用 java.beans.PropertyEditorManager 来设置可能需要的属性编辑器的搜索路径。该搜索路径还包括 sun.bean.editors,其中包含针对诸如 FontColor 以及大多数基本类型等类型的 PropertyEditor 实现。另外请注意,标准的 JavaBeans 基础设施会自动发现 PropertyEditor 类(无需您显式注册),只要它们与所处理的类位于同一个包中,并且名称与该类相同,只是追加了 Editor。例如,可以拥有以下的类和包结构,这足以让 SomethingEditor 类被识别并用作 Something 类型属性的 PropertyEditorspring-doc.cadn.net.cn

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

请注意,您也可以在此处使用标准的 BeanInfo JavaBeans 机制(在此处有部分描述)。以下示例使用 BeanInfo 机制,为关联类的属性显式注册一个或多个 PropertyEditor 实例:spring-doc.cadn.net.cn

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

以下所引用的 SomethingBeanInfo 类的 Java 源代码将 CustomNumberEditorage 类的 Something 属性关联起来:spring-doc.cadn.net.cn

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 采用适当的名称,并放置在该类所在的同一个包中,从而能够被自动发现。spring-doc.cadn.net.cn

如果需要注册其他自定义的PropertyEditors,有几种机制可供使用。最手动的方式(通常既不方便也不推荐)是使用registerCustomEditor()接口的ConfigurableBeanFactory方法,前提是你持有BeanFactory的引用。另一种(稍微更方便的)机制是使用一个名为CustomEditorConfigurer的特殊 Bean 工厂后置处理器。虽然你可以在BeanFactory实现中使用 Bean 工厂后置处理器,但CustomEditorConfigurer具有嵌套属性配置,因此我们强烈建议将其与ApplicationContext一起使用,这样你可以像部署其他任何 Bean 一样部署它,并且它可以被自动检测和应用。spring-doc.cadn.net.cn

请注意,所有 BeanFactory 和 ApplicationContext 都会通过使用 BeanWrapper 来处理属性转换,从而自动使用若干内置的属性编辑器。BeanWrapper 注册的标准属性编辑器已在前一节中列出。 此外,ApplicationContext 还会覆盖或添加额外的编辑器,以适合特定应用上下文类型的方式来处理资源查找。spring-doc.cadn.net.cn

标准的 JavaBeans PropertyEditor 实例用于将字符串形式表示的属性值转换为该属性的实际复杂类型。您可以使用 CustomEditorConfigurer(一种 BeanFactory 后置处理器)方便地向 PropertyEditor 中添加对额外 ApplicationContext 实例的支持。spring-doc.cadn.net.cn

考虑以下示例,其中定义了一个名为 ExoticType 的用户类, 以及另一个名为 DependsOnExoticType 的类,该类需要将 ExoticType 设置为一个属性:spring-doc.cadn.net.cn

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 定义展示了如何建立这种关系:spring-doc.cadn.net.cn

<bean id="sample" class="example.DependsOnExoticType">
	<property name="type" value="aNameForExoticType"/>
</bean>

PropertyEditor 的实现可能类似于以下内容:spring-doc.cadn.net.cn

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 中,之后该上下文便可在需要时使用它:spring-doc.cadn.net.cn

<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(..) 的属性。以这种方式添加到 CustomEditorConfigurerPropertyEditorRegistrar 实例可以轻松地与 DataBinder 和 Spring MVC 控制器共享。此外,它避免了对自定义编辑器进行同步的需要:PropertyEditorRegistrar 预期会为每次 Bean 创建尝试生成全新的 PropertyEditor 实例。spring-doc.cadn.net.cn

以下示例展示了如何创建您自己的 PropertyEditorRegistrar 实现:spring-doc.cadn.net.cn

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(..) 方法的实现中,它是如何为每个属性编辑器创建新实例的。spring-doc.cadn.net.cn

下一个示例展示了如何配置一个 CustomEditorConfigurer,并将我们自定义的 CustomPropertyEditorRegistrar 实例注入其中:spring-doc.cadn.net.cn

<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 会非常方便。以下示例在 PropertyEditorRegistrar 方法的实现中使用了 @InitBinderspring-doc.cadn.net.cn

@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 注册代码封装在一个类中,然后在任意多个控制器之间共享。spring-doc.cadn.net.cn