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

Bean操作与BeanWrapper

The org.springframework.beans package adheres to the JavaBeans standard. A JavaBean is a class with a default no-argument constructor and that follows a naming convention where (for example) a property named bingoMadness would have a setter method setBingoMadness(..) and a getter method getBingoMadness(). For more information about JavaBeans and the specification, see javabeans.spring-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

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

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

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

namespring-doc.cadn.net.cn

表示对应 name 属性的 getName()isName()setName(..) 方法。spring-doc.cadn.net.cn

account.namespring-doc.cadn.net.cn

指示属性 name 的嵌套属性,该属性对应(例如)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

(如果不需要直接使用<code>0</code>,那么下一节对你来说并不至关重要。如果你只使用<code>1</code>和<code>2</code>及其默认实现,可以跳到<a t="C4">关于<code>3</code>的章节</a>。)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框架中,通过使用各种PropertyEditor实现来解析HTTP请求参数,您可以手动绑定所有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

可以将字符串(使用 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 基础设施如果在处理类所在的同一包中,并且名称与该类相同(加上 Editor),则会自动发现 PropertyEditor 类(而无需您显式注册它们)。例如,可以有以下类和包结构,这将足以让 SomethingEditor 类被识别并用作 PropertyEditor 用于 Something 类型的属性。spring-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() 接口的 registerCustomEditor() 方法,假设你有一个 BeanFactory 的引用。另一种(稍微更方便)机制是使用一个特殊的 Bean 工厂后处理器,称为 CustomEditorConfigurer。虽然你可以使用 Bean 工厂后处理器与 BeanFactory 实现,但 CustomEditorConfigurer 有一个嵌套属性设置,因此我们强烈建议你与 ApplicationContext 一起使用,你可以在类似其他 Bean 的方式中部署它,并且它可以被自动检测和应用。spring-doc.cadn.net.cn

请注意,所有bean工厂和应用上下文通过使用BeanWrapper来处理属性转换,自动使用了许多内置的属性编辑器。BeanWrapper注册的标准属性编辑器列在上一节中。 此外,ApplicationContexts 还会覆盖或添加其他编辑器,以适当的方式处理特定应用上下文类型的资源查找。spring-doc.cadn.net.cn

标准的 JavaBeans PropertyEditor 实例用于将作为字符串表达的属性值转换为属性的实际复杂类型。您可以使用 CustomEditorConfigurer,一个 bean 工厂后处理器,方便地向 ApplicationContext 添加对额外 PropertyEditor 实例的支持。spring-doc.cadn.net.cn

考虑以下示例,该示例定义了一个名为 ExoticType 的用户类和另一个需要将 ExoticType 设置为属性的类 DependsOnExoticTypespring-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

注册属性编辑器的另一种机制是创建并使用一个PropertyEditorRegistrar。此接口在需要在几种不同情况下使用相同的一组属性编辑器时特别有用。您可以编写相应的注册器并在每种情况下重复使用它。 PropertyEditorRegistrar实例与称为PropertyEditorRegistry的接口协同工作,该接口由Spring的BeanWrapper(和DataBinder)实现。PropertyEditorRegistrar实例在与CustomEditorConfigurer(在这里描述)结合使用时特别方便,它公开了一个名为setPropertyEditorRegistrars(..)的属性。PropertyEditorRegistrar实例以这种方式添加到CustomEditorConfigurer中,可以轻松与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框架的用户来说,将PropertyEditorRegistrar与数据绑定的Web控制器一起使用可能会非常方便。下面的示例在实现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