首页 > 代码库 > 6.4 Bean操作和BeanWrapper类之二

6.4 Bean操作和BeanWrapper类之二

内置的PropertyEditor实现

Spring使用PropertyEditors的概念来实现ObjectString的转换。如果你考虑它(PropertyEditors),那么有时候使用它来表示属性可能比对象本身更容易。比如,Date可以被表示成人类易读的方式(作为String ‘2008-14-09‘),并且我们还可以将它转换成原始的Date(或者更好:将任何输入可读的日期,转换成Date对象)。这可以通过注册自定义editorsjava.beans.PropertyEditor类型)来实现。通过在一个BeanWrapper上或者在给定的IoC容器中注册自定义的editors,告诉它(BeanWrapper)如何将属性转换为想要的类型。可以在java.bens包的Javadoc中阅读到更多关于PropertyEditors的信息。

Spring中几个用到属性编辑的例子:

1、设置beans属性的时候用到了PropertyEditors。当String作为一个属性值定义在XML文件中时,Spring将会(如果对应属性的set方法有一个类参数的话)使用ClassEditor来将该String参数解析成一个Class对象

2、在Spring MVC框架中使用多种PropertyEditors来解析HTTP请求参数,并且你也可以在CommandController的所有子类中手动绑定。

Spring拥有一系列的内置PropertyEditors来简化工作。下面列出了每一个PropertyEditor,并且他们都是在org.springframework.beans.propertyeditors包下。大多数,但并不是所有的(如下所示)PropertyEditor默认就被BeanWrapperImpl注册。不管它以何种方式注册,你都可以注册你自己的PropertyEditor来覆盖默认值。

下面对Spring中内置的PropertyEditor就不一一列举出来了,拿几个常用的来介绍介绍(译者添加)

ClassEditorStringClass之间的转换,当然若String表示的类找不到,会报异常

CustomDateEditorStringDate之间的转换,并未被默认注册。使用时必须手动注册,并且给定一个合适的格式化形式。

CustomNumberEditorStringNumberIntegerLongFloatDouble)之间的转换。默认注册,但是可以被覆盖。

Spring使用java.beans.PropertyEditorManager来设置属性编辑器的搜索路径。搜索路径也包括sun.bean.editors,这个包里包括了比如FontColor等大多数原始类型(primitive typesPropertyEditor的实现。需要注意的是,如果PropertyEditor类和标准的 JavaBeans在同一个包下,并且他们PropertyEditor的类名是JavaBeans的类名加上“Editor”,那么JavaBeans的基础结构会自动发现PropertyEditor的类(而无需你去注册它们)。比如,下面的类和包结构就足够将FooEditor类当做Foo类型的属性的PropertyEditor

com
  chank
    pop
      Foo
      FooEditor  // the PropertyEditorfor the Fooclass

在这里你还可以使用标准的BeanInfo JavaBeans机制。下面的例子是使用BeanInfo机制来精确地为一个相关类的属性注册一个或多个PropertyEditor实例。

com
  chank
    pop
      Foo
      FooBeanInfo  // the BeanInfofor the Fooclass

下面是FooBeanInfo类的源代码。它会将Foo类的age属性关联到一个CustomNumberEditor

public class FooBeanInfo extends SimpleBeanInfo {
	public PropertyDescriptor[] getPropertyDescriptors() {
		try {
			final PropertyEditor numberPE = new CustomNumberEditor(
					Integer.class, true);
			PropertyDescriptor ageDescriptor = new PropertyDescriptor("age",
					Foo.class) {
				public PropertyEditor createPropertyEditor(Object bean) {
					return numberPE;
				};
			};
			return new PropertyDescriptor[] { ageDescriptor };
		} catch (IntrospectionException ex) {
			throw new Error(ex.toString());
		}
	}
}

注册额外的自定义PropertyEditors

当使用String来设置bean属性时,SpringIoC容器底层都会使用JavaBeans PropertyEditors来将Strings转换成该属性的复杂类型(译者注:也有可能是Integer等简单类型)。Spring预先注册了一部分自定义的PropertyEditors(比如,将一个classname转换成真正的Class对象)。另外,Java的标准JavaBeans PropertyEditor查找机制允许针对某个类的PropertyEditor只需要合适的命名并且和JavaBeans放在同一个包下,那么就会被自动找到。

如果还需要注册额外的自定义PropertyEditors,可以用到下面这些机制:最原始的方法,但并不推荐,因为不太方便,假设你有一个BeanFactory的引用,那么你只要调用ConfigurableBeanFactory接口的registerCustomEditor()方法就可以了。另一种稍微方便一点的方法,使用一个特殊的bean factory后置处理器 (post-processor)——CustomEditorConfigurer.尽管bean factory post-processors可以与BeanFactory的实现一起使用,但是CustomEditorConfigurer还是有一个嵌套的 属性安装,所以强烈建议与ApplicationContext一起使用,因为这样它可以使用与其他bean相似的方式来部署,并且可以被自动检测和应用。

注意,尽管所有的bean factoriesapplication contexts都使用一个叫做BeanWrapper的东西来处理属性之间的转换,但它们还是会自动使用一些内建的property editors。关于BeanWrapper会注册的标准property editors,这在前面一节中已经列举过了。

另外,对于特定的应用上下文类型,ApplicationContexts也会在某种程度上重写或是添加一个额外数量的editors来处理资源的寻找。

标准的JavaBeans PropertyEditor实例是用来将String属性值转换成该属性真正的复杂类型。CustomEditorConfigurer是一个bean factory post-processor,它可以用来便捷地为ApplicationContext中额外注册的PropertyEditor实例来添加支持。

考虑一个类ExoticType和另一个拥有ExoticType属性的DependsOnExoticType

package document.six.test;

public class ExoticType {
	private String name;

	public ExoticType(String name) {
		this.name = name;
	}
}

package document.six.test;

public class DependsOnExoticType {
	private ExoticType type;

	public void setType(ExoticType type) {
		this.type = type;
	}

	public ExoticType getType() {
		return type;
	}
}

当一切都建立好之后,我们想要为ExoticType属性指定一个string,并且让PropertyEditor在底层将这个string转换成一个实际的ExoticType的实例。

	<bean id="sample" class="document.six.test.DependsOnExoticType">
		<property name="type" value=http://www.mamicode.com/"aNameForExoticType" />>

PropertyEditor的实现看起来应该类似:(其实不需要这个PropertyEditor实现也可以,具体可以看这篇文章更详细)

package document.six.test;

import java.beans.PropertyEditorSupport;

//converts string representation to ExoticTypeobject
public class ExoticTypeEditor extends PropertyEditorSupport {
	public void setAsText(String text) {
		setValue(new ExoticType(text.toUpperCase()));
	}
}

最后,我们使用CustomEditorConfigurerApplicationContext中来注册这个新的PropertyEditor,之后我们就可以在需要的时候使用它了。

	<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
		<property name="customEditors">
			<map>
				<entry key="document.six.test.ExoticType" value=http://www.mamicode.com/"document.six.test.ExoticTypeEditor" />>

使用PropertyEditorRegistrars

创建并使用一个PropertyEditorRegistrar是另一个在Spring容器中注册property editors的方法。当你需要在一些不同的场景下使用同一组property editors,那么这个接口将会变得非常有用。比如:写一个通用的registrar然后在多种情况下重用。PropertyEditorRegistrars要和PropertyEditorRegistry一起工作,SpringBeanWrapper(和DataBinder)实现了PropertyEditorRegistry这个接口。当你与CustomEditorConfigurer一起使用时,PropertyEditorRegistrars将会变得相当方便。CustomEditorConfigurer暴露了一个属性叫做setPropertyEditorRegistrars(..):用这种方式添加的PropertyEditorRegistrars可以很容易地与DataBinderSpring MVC控制器共享。而且,它避免了自定义editors同步的问题:PropertyEditorRegistrar会为每个bean的创建尝试都创建一个新的PropertyEditor实例。

可能用一个例子可以更好的阐述如何使用一个PropertyEditorRegistrar。首先,你需要创建你自己的PropertyEditorRegistrar实现:

package document.six.test;

import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;

public final class CustomPropertyEditorRegistrar implements
		PropertyEditorRegistrar {
	public void registerCustomEditors(PropertyEditorRegistry registry) {
		// it is expected that new PropertyEditorinstances are created
		registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
		// you could register as many custom property editors as are required
		// here...
	}
}

可以把org.springframework.beans.support.ResourceEditorRegistrar这个类当成一个PropertyEditorRegistrar实现的例子来学习。注意在这个实现中registerCustomEditors(..)方法是如何为每个property editor都创建一个新的实例的

下面我们配置一个CustomEditorConfigurer然后向其中注入一个CustomPropertyEditorRegistrar实例

	<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
		<property name="propertyEditorRegistrars">
			<list>
				<ref bean="customPropertyEditorRegistrar" />
			</list>
		</property>
	</bean>
	<bean id="customPropertyEditorRegistrar" class="document.six.test.CustomPropertyEditorRegistrar" />

最后,稍微偏离一下本章的主题,对那些使用Spring MVC web框架的朋友来说,使用PropertyEditorRegistrarsdata-binding Controllers(比如SimpleFormController)一起工作将会非常方便。下面的例子就是在initBinder(..)的实现中使用一个PropertyEditorRegistrar(译者注:这里若是基于注解的Controller,那么在initBinder(..)方法上也需要打一个注解@InitBinder

public final class RegisterUserController extends SimpleFormController {
	private final PropertyEditorRegistrar customPropertyEditorRegistrar;

	public RegisterUserController(
			PropertyEditorRegistrar propertyEditorRegistrar) {
		this.customPropertyEditorRegistrar = propertyEditorRegistrar;
	}

	protected void initBinder(HttpServletRequest request,
			ServletRequestDataBinder binder) throws Exception {
		this.customPropertyEditorRegistrar.registerCustomEditors(binder);
	}
}

这种方式的PropertyEditor注册有助于构建一个简洁明了的代码(initBinder(..)只有一行),并且允许公共的PropertyEditor注册代码被封装在一个类中,之后可以在许多Controllers中共享。


6.4 Bean操作和BeanWrapper类之二