Spring PropertyEditor分析

简介

通过xml解析器解析出bean定义之后,解析出来的是一个一个的字符串,但是bean的属性可以各种java类型,那么在对bean进行初始化时需要在这些字符串和java类型之间进行转换,比如我的bean有个Class属性,那么在注入这个Class属性时在只能给property设置一个字符串value="a.b.C" ,框架在最终填充这个属性的时候需要把"a.b.C"转换成Class对象,这项工作是通过什么来完成的呢?它就是几天这篇博客的主角,PropertyEditor。

PropertyEditor的处理过程如下:

  • 调用setAsText把待处理的text传递到PropertyEditor,PropertyEditor把text转换成相应类型的值并且保存到PropertyEditor
  • 调用getValue把获取上面步骤转换后的value

通过上面的过程可以发现PropertyEditor保存了value,所以是非线程安全的,因此在值转换的过程中需要做同步

内置PropertyEditor

Spring框架内置了一些PropertyEditor来对,并且bean填充属性之前都会把这些PropertyEditor添加到BeanWrapper中(在Spring IOC容器bean初始化源码分析有分析),代码在PropertyEditorRegistrySupport类的createDefaultEditors方法中:

private void createDefaultEditors() {
	this.defaultEditors = new HashMap<Class<?>, PropertyEditor>(64);

	// Simple editors, without parameterization capabilities.
	// The JDK does not contain a default editor for any of these target types.
	this.defaultEditors.put(Charset.class, new CharsetEditor());
	this.defaultEditors.put(Class.class, new ClassEditor());
	this.defaultEditors.put(Class[].class, new ClassArrayEditor());
	this.defaultEditors.put(Currency.class, new CurrencyEditor());
	this.defaultEditors.put(File.class, new FileEditor());
	this.defaultEditors.put(InputStream.class, new InputStreamEditor());
	this.defaultEditors.put(InputSource.class, new InputSourceEditor());
	this.defaultEditors.put(Locale.class, new LocaleEditor());
	this.defaultEditors.put(Pattern.class, new PatternEditor());
	this.defaultEditors.put(Properties.class, new PropertiesEditor());
	this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
	this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
	this.defaultEditors.put(URI.class, new URIEditor());
	this.defaultEditors.put(URL.class, new URLEditor());
	this.defaultEditors.put(UUID.class, new UUIDEditor());

	// Default instances of collection editors.
	// Can be overridden by registering custom instances of those as custom editors.
	this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
	this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
	this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
	this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
	this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));

	// Default editors for primitive arrays.
	this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
	this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());

	// The JDK does not contain a default editor for char!
	this.defaultEditors.put(char.class, new CharacterEditor(false));
	this.defaultEditors.put(Character.class, new CharacterEditor(true));

	// Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.
	this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
	this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));

	// The JDK does not contain default editors for number wrapper types!
	// Override JDK primitive number editors with our own CustomNumberEditor.
	this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));
	this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
	this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));
	this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
	this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
	this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
	this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
	this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
	this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
	this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
	this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
	this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
	this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
	this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));

	// Only register config value editors if explicitly requested.
	if (this.configValueEditorsActive) {
		StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
		this.defaultEditors.put(String[].class, sae);
		this.defaultEditors.put(short[].class, sae);
		this.defaultEditors.put(int[].class, sae);
		this.defaultEditors.put(long[].class, sae);
	}
}
此外还内置了一些Resource相关的PropertyEditor,代码在ResourceEditorRegistrar类的registerCustomEditors方法中:
public void registerCustomEditors(PropertyEditorRegistry registry) {
	ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader, this.propertyResolver);
	doRegisterEditor(registry, Resource.class, baseEditor);
	doRegisterEditor(registry, ContextResource.class, baseEditor);
	doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor));
	doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor));
	doRegisterEditor(registry, File.class, new FileEditor(baseEditor));
	doRegisterEditor(registry, URL.class, new URLEditor(baseEditor));

	ClassLoader classLoader = this.resourceLoader.getClassLoader();
	doRegisterEditor(registry, URI.class, new URIEditor(classLoader));
	doRegisterEditor(registry, Class.class, new ClassEditor(classLoader));
	doRegisterEditor(registry, Class[].class, new ClassArrayEditor(classLoader));

	if (this.resourceLoader instanceof ResourcePatternResolver) {
		doRegisterEditor(registry, Resource[].class,
				new ResourceArrayPropertyEditor((ResourcePatternResolver) this.resourceLoader, this.propertyResolver));
	}
}
ResourceEditorRegistrar是框架内置的一个PropertyEditor注册器,它是一个BFPP,在ClasspathXmlApplicationContext启动时就会把它添加到容器中,具体的代码分析在 Spring IOC容器启动简介中。

自定义PropertyEditor

当Spring内置的PropertyEditor无法满足我们的要求的时候,我们可以根据Spring提供的扩展机制来自定义PropertyEditor,下面通过一个例子来介绍如何实现自定义的PropertyEditor,我的这个PropertyEditor是一个时间相关的Editor,它可以一个满足特定时间格式的字符串转换成日期对象。

首先定义一个PropertyEditor,框架中提供了一个PropertyEditor基类PropertyEditorSupport,直接继承这个类可以省去一部分代码,代码如下:

public class DateEditor extends PropertyEditorSupport {
	private DateFormat dateFormat;

	public void setDateFormat(String dateFormat) {
		this.dateFormat = new SimpleDateFormat(dateFormat);
	}

	@Override
	public void setAsText(String text) throws IllegalArgumentException {
		try {
			Object value = dateFormat.parse(text);
			setValue(value);
		} catch (ParseException e) {
			e.printStackTrace();
		}
	}

	@Override
	public String getAsText() {
		if (getValue() instanceof Date) {
			Date d = (Date) getValue();
			return dateFormat.format(d);
		}
		return super.getAsText();
	}

}
dateFormat是日期格式,需要使用者注入

接下来要把DateEditor注入到到容器中,框架提供了一个BFPP 类CustomEditorConfigurer来完成这项工作,我们只要在DateEditor注入到这个BFPP bean中就可以了,有以下几种注入方式:

通过customEditors属性注入,它是CustomEditorConfigurer中 一个Map属性,key是类型字符串,value是PropertyEditor实例,xml配置片段如下:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
	<property name="customEditors">
		<map>
			<entry key="java.util.Date" value-ref="dateEditor"></entry>
		</map>
	</property>
</bean>
<bean id="dateEditor" class="spring.beans.propertyeditor.DateEditor">
	<property name="dateFormat" value="yyyy-MM-dd HH:mm:ss" />
</bean>
通过一个测试bean和一段测试代码测试一下:

<bean id="dateBean" class="spring.beans.propertyeditor.DateBean">
	<constructor-arg value="2014-06-01 09:21:20"></constructor-arg>
</bean>
@Test
public void test() {
	BeanFactory context = new ClassPathXmlApplicationContext(
			"spring/beans/propertyeditor/propertyeditor1.xml");
	DateBean dateBean = (DateBean) context.getBean("dateBean");
	System.out.println(dateBean.getDate());
}
打印结果

Sun Jun 01 09:21:20 CST 2014
说明日志转成功了

但是这种方式有一个问题,在这种配置方式下,PropertyEditor是共享的,在博客最开始的时候提到了PropertyEditor是非线程安全的,所以在处理的过程中需要做同步,这意味着在在高并发坏境下这里的同步会对应用的性能造成比较大的影响。所以这种方式是不推荐的,事实上这种方式已经被Spring打上deprecated标签了。

为了解决上面提到的可能引发的性能问题,我们可以修改配置:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
	<property name="customEditors">
		<map>
			<entry key="java.util.Date" value="spring.beans.propertyeditor.DateEditor"></entry>
		</map>
	</property>
</bean>
上面这段配置是把DateEditor的class注入到customEditors属性中,这种情况下在bean初始化时会创建一个新的PropertyEditor对象注册到BeanWrapper中(可以参考 Spring IOC容器bean初始化源码分析中的分析),这样PropertyEditor是不同的对象状态是独立的,就不会有上面提到的同步问题。但是还是有个问题,这个DateEditor是需要注入属性的,而PropertyEditor的实例化又不能被应用层干预,所以无法注入这个dateFormat属性到DateEditor对象中,因此这种方式是不能满足我们的需求的,它只能应用在PropertyEditor不需要注入属性的情况。对于我们的需求只能通过下面这种方式来实现。

通过propertyEditorRegistrars注入,它是CustomEditorConfigurer的一个PropertyEditor注册器数组,它的职责是用来注册PropertyEditor,而且可以注册任意数量的PropertyEditor,首先需要定义一个注册器:

public class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

	private String dateFormat;

	public String getDateFormat() {
		return dateFormat;
	}

	public void setDateFormat(String dateFormat) {
		this.dateFormat = dateFormat;
	}

	@Override
	public void registerCustomEditors(PropertyEditorRegistry registry) {
		DateEditor dateEditor = new DateEditor();
		dateEditor.setDateFormat(dateFormat);
		registry.registerCustomEditor(Date.class, dateEditor);

	}

}
在注册器的registerCustomEditors方法中把DateEditor注册到容器中并且和Date类型绑定,要确保DateEditor对象时临时创建的对象而不是一个全局对象,否则一样可能会引发性能问题。把日期格式通过dateFormat字段注入,然后修改xml片段:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
	<property name="propertyEditorRegistrars">
		<list>
			<bean class="spring.beans.propertyeditor.CustomPropertyEditorRegistrar">
				<property name="dateFormat" value="yyyy-MM-dd HH:mm:ss"></property>
			</bean>
		</list>
	</property>
</bean>
这种方式下,每个BeanWrapper都会注册不同的PropertyEditor,不会有高并发下的性能问题,而且用法也比较灵活,所以Spring的推荐是通过propertyEditorRegistrars注入来完成PropertyEditor的自定义。

代码原理

下面来看看关于框架中关于自定义PropertyEditor的实现

看下CustomEditorConfigurer类的postProcessBeanFactory方法:

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
	if (this.propertyEditorRegistrars != null) {
		for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
			beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar);
		}
	}

	if (this.customEditors != null) {
		for (Map.Entry<String, ?> entry : this.customEditors.entrySet()) {
			String key = entry.getKey();
			Object value = entry.getValue();
			Class requiredType = null;

			try {
				requiredType = ClassUtils.forName(key, this.beanClassLoader);
				if (value instanceof PropertyEditor) {
					...
					beanFactory.addPropertyEditorRegistrar(
							new SharedPropertyEditorRegistrar(requiredType, (PropertyEditor) value));
				}
				else if (value instanceof Class) {
					beanFactory.registerCustomEditor(requiredType, (Class) value);
				}
				else if (value instanceof String) {
					Class editorClass = ClassUtils.forName((String) value, this.beanClassLoader);
					Assert.isAssignable(PropertyEditor.class, editorClass);
					beanFactory.registerCustomEditor(requiredType, editorClass);
				}
				else {
					...
				}
			}
			catch (ClassNotFoundException ex) {
				...
			}
		}
	}
}


从代码中可以看到:

把propertyEditorRegistrars中所有的PropertyEditor注册器通过调用beanFactory的addPropertyEditorRegistrar方法注册到容器中,保存在propertyEditorRegistrars集合属性中

如果customEditors属性value的类型为String或Class,调用beanFactory的registerCustomEditor把自定义PropertyEditor的Class添加到容器中(String转换成Class),保存在容器的customEditors哈希表属性中,key为值类型,value为PropertyEditor类型。

如果customEditors属性value是一个PropertyEditor实例,那么注册一个PropertyEditor注册器SharedPropertyEditorRegistrar到容器中(同样是调用beanFactory的addPropertyEditorRegistrar方法),该注册器保存这个PropertyEditor实例,并且BeanWrapper进行初始化工作时会把这个PropertyEditor实例注册到BeanWrapper中,无论哪个BeanWrapper注册的都是这同一个实例。

通过Spring IOC容器bean初始化源码分析中的分析,bean实例化时会调用AbstractBeanFactory的initBeanWrapper方法,这个方法紧接着都调用registerCustomEditors方法,看看registerCustomEditors方法中的代码:

protected void registerCustomEditors(PropertyEditorRegistry registry) {
	PropertyEditorRegistrySupport registrySupport =
			(registry instanceof PropertyEditorRegistrySupport ? (PropertyEditorRegistrySupport) registry : null);
	if (registrySupport != null) {
		registrySupport.useConfigValueEditors();
	}
	if (!this.propertyEditorRegistrars.isEmpty()) {
		for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) {
			try {
				registrar.registerCustomEditors(registry);
			}
			catch (BeanCreationException ex) {
				...
			}
		}
	}
	if (!this.customEditors.isEmpty()) {
		for (Map.Entry<Class<?>, Class<? extends PropertyEditor>> entry : this.customEditors.entrySet()) {
			Class<?> requiredType = entry.getKey();
			Class<? extends PropertyEditor> editorClass = entry.getValue();
			registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass));
		}
	}
}
registry这个参数就是BeanWrapper实例(BeanWrapperImpl实现了PropertyEditorRegistry接口,并且继承了PropertyEditorRegistrySupport类)从代码中可以看到:

  • 首先调用所有框架内置和自定义PropertyEditor注册器的registerCustomEditors方法,这个方法把执行PropertyEditor注册到BeanWrapper逻辑,比如在我们的CustomPropertyEditorRegistrar注册器就是就是通过registry.registerCustomEditor 方法把DateEditor实例添加到BeanWrapper中,注意这个实例是新鲜出炉的哟。而在上面提到的SharedPropertyEditorRegistrar注册器中,它里面持有一个之前注册进来的PropertyEditor对象,这个对象实例始终是同一个,是共享的,这个注册器在注册PropertyEditor时会先调用PropertyEditorRegistrySupport.registerSharedEditor方法,但是最终还是会调到PropertyEditorRegistrySupport.registerCustomEditor的方法,registerCustomEditor这个方法的作用就是把PropertyEditor保存在customEditors这个Map属性中,key是类型,value是PropertyEditor实例,BeanWrapper在进行值转换时直接从这个Map中获取相关的PropertyEditor。
  • 接下来处理customEditors中的PropertyEditor,这里面的value都收Class,所以先根据Class创建一个实例,然后调用registry.registerCustomEditor 把创建好的实例注册到BeanWrapper中,实例也是新鲜出炉的。

以上是关于Spring中PropertyEditor的全部内容。

  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值