在文章《spring源码分析-PropertyEditor》中,已经介绍了什么是PropertyEditor以及它用来干嘛。其实,简单地说,它就是用于特定类型与字符串之间的转换的,只不过在不同的场景有不同的作用,例如:
(1)在从spring容器获取对象时,PropertyEditor对xml中bean定义的value值进行了类型转换,并通过内省的方式设置到对象中。
(2)spring mvc获取request的参数,当我们需要把param绑定到controller的对象参数时,需要使用ConversionService和PropertyEditor配合对字符串进行类型转换(这句话,请参考在评论区中的答案),设置到绑定对象。
在这里,不再赘述它的一些概念和原理,如果不清楚它的概念的话,还是建议先看一下《spring源码分析-PropertyEditor》,因为本文将会作为该文章的补充。
在本文我们将探讨如何在spring中使用自定义的PropertyEditor,然后比较自定义PropertyEditor在spring不同版本中的注册方式,除此之外,我们还将简单剖析一下CustomEditorConfigurer的源码。
需求与实现
先了解一下本文的需求。我们需要通过spring为order实例注入createDate。order类和spring 中的bean配置如下:
public class Order {
private Date createDate;
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
@Override
public String toString() {
return "Order{" +
"createDate=" + createDate +
'}';
}
}
<bean id="order" class="com.github.thinwonton.spring.source.analysis.propertyeditor.Order">
<property name="createDate" value="2016-07-09 23:44:18"/>
</bean>
显然,这样是不能注入成功的,并且会抛出异常,这是因为spring默认情况下没有注册解决java.util.Date的属性编辑器,既然没有注册,那么接下来就抓紧注册一个属性编辑器解决类型转换问题吧。
定义一个解决Date类型的属性编辑器
package com.github.thinwonton.spring.source.analysis.propertyeditor;
import org.springframework.format.datetime.DateFormatter;
import org.springframework.util.StringUtils;
import java.beans.PropertyEditorSupport;
import java.text.ParseException;
import java.util.Date;
import java.util.Locale;
public class CustomDateEditor extends PropertyEditorSupport {
/**
* Date的转换模板
*/
private final String pattern;
private final boolean allowEmpty;
private final DateFormatter dateFormatter;
public CustomDateEditor(String pattern) {
this(pattern, true);
}
public CustomDateEditor(String pattern, boolean allowEmpty) {
this.pattern = pattern;
this.allowEmpty = allowEmpty;
this.dateFormatter = new DateFormatter(this.pattern);
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (!StringUtils.hasText(text) && this.allowEmpty) {
// 字符为""或者null,并且属性编辑器允许属性为null,把属性设为null
setValue(null);
} else if (!StringUtils.hasText(text)) {
// 字符为""或者null,并且属性编辑器不允许属性为null,抛异常
throw new IllegalArgumentException(
"date string is empty, but allowEmpty argument is false");
} else {
try {
setValue(this.dateFormatter.parse(text, Locale.getDefault()));
} catch (ParseException ex) {
throw new IllegalArgumentException("Could not parse date: " + ex.getMessage(), ex);
}
}
}
@Override
public String getAsText() {
Date value = (Date) getValue();
return (value != null ? this.dateFormatter.print(value, Locale.getDefault()) : "");
}
}
Date的属性编辑器将从xml的value属性中读取字符串,根据配置好的模板转换成Date对象,设置值。
定义好属性编辑器后,我们需要把它注册到spring,由它进行管理。
注册自定义的属性编辑器
自定义的属性编辑器注册,在spring 3.x版本和4.x版本的注册方式有点不同。
我们先看一下spring 3.x版本的注册。
spring 3.x版本的注册方式
<bean id="customDateEditor"
class="com.github.thinwonton.spring.source.analysis.propertyeditor.CustomDateEditor">
<constructor-arg name="allowEmpty" value="true"/>
<constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"/>
</bean>
<bean id="customEditorConfigurer"
class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.util.Date">
<ref bean="customDateEditor"/>
</entry>
</map>
</property>
</bean>
一起看一下spring 3.x版本中CustomEditorConfigurer的customEditors属性
private Map<String, ?> customEditors;
public void setCustomEditors(Map<String, ?> customEditors) {
this.customEditors = customEditors;
}
customEditors是一个map结构的属性,它的value是可以接受任何类型的。那么这些自定义的属性编辑器是在什么时候注册的呢?
CustomEditorConfigurer实现了后置处理器BeanFactoryPostProcessor,容器初始化时会调用它的postProcessBeanFactory方法。直接看一下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) {
if (logger.isWarnEnabled()) {
logger.warn("Passing PropertyEditor instances into CustomEditorConfigurer is deprecated: " +
"use PropertyEditorRegistrars or PropertyEditor class names instead. " +
"Offending key [" + key + "; offending editor instance: " + value);
}
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 {
throw new IllegalArgumentException("Mapped value [" + value + "] for custom editor key [" +
key + "] is not of required type [" + PropertyEditor.class.getName() +
"] or a corresponding Class or String value indicating a PropertyEditor implementation");
}
}
catch (ClassNotFoundException ex) {
if (this.ignoreUnresolvableEditors) {
logger.info("Skipping editor [" + value + "] for required type [" + key + "]: " +
(requiredType != null ? "editor" : "required type") + " class not found.");
}
else {
throw new FatalBeanException(
(requiredType != null ? "Editor" : "Required type") + " class not found", ex);
}
}
}
}
}
这段代码很简单,就是propertyEditorRegistrars 不为空时,把它添加到spring的bean工厂中,其实propertyEditorRegistrars也保存了自定义属性编辑器,它会在之后由spring取出来注册到容器中;后面的代码是遍历customEditors,判断entry的value类型,如果是PropertyEditor类型就使用注册器添加到bean工厂;如果是class类型就直接注册,后面再实例化。
其实,这段代码透露给我们另一种注册属性编辑器的方法,就是通过propertyEditorRegistrars注册。这个也是spring 4.x采用的方法,该方法放在spring 4.x部分介绍。
spring 4.x版本的注册方式
一起翻一下CustomEditorConfigurer的源码吧
private Map<Class<?>, Class<? extends PropertyEditor>> customEditors;
public void setCustomEditors(Map<Class<?>, Class<? extends PropertyEditor>> customEditors) {
this.customEditors = customEditors;
}
customEditors的数据结构发生了变化,尽管它还是map,但它的value变成了class了,也就意味着,我们还是想通过customEditors注入属性编辑器,只能注入class了。注入class后,将由spring负责实例化。有个非常值得关注的问题是,spring实例化bean,在没有指定构造方法的时候,是使用无参构造方法实例化的,回头看一下前面定义的CustomDateEditor,没有无参构造方法。行不通啊。
接下来看一下postProcessBeanFactory方法的源码。
@Override
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<Class<?>, Class<? extends PropertyEditor>> entry : this.customEditors.entrySet()) {
Class<?> requiredType = entry.getKey();
Class<? extends PropertyEditor> propertyEditorClass = entry.getValue();
beanFactory.registerCustomEditor(requiredType, propertyEditorClass);
}
}
}
propertyEditorRegistrars 这段代码还存在。也就是说,不管spring 3.x或者4.x,我们都可以通过propertyEditorRegistrars 注入属性编辑器,从而注册我们的属性编辑器。
接下来,将介绍如何通过CustomEditorConfigurer的propertyEditorRegistrars 属性,注入我们的属性编辑器。
实现PropertyEditorRegistrar
package com.github.thinwonton.spring.source.analysis.propertyeditor;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
import java.beans.PropertyEditor;
import java.util.Map;
import java.util.Set;
public class CustomEditorRegistrar implements PropertyEditorRegistrar {
private Map<Class<?>, PropertyEditor> customEditors;
public void registerCustomEditors(PropertyEditorRegistry registry) {
if (customEditors != null) {
Set<Map.Entry<Class<?>, PropertyEditor>> entries = customEditors.entrySet();
for (Map.Entry<Class<?>, PropertyEditor> entry : entries) {
registry.registerCustomEditor(entry.getKey(), entry.getValue());
}
}
}
public void setCustomEditors(Map<Class<?>, PropertyEditor> customEditors) {
this.customEditors = customEditors;
}
}
定义了一个customEditors属性,可以在xml中注入属性编辑器。
spring中配置PropertyEditorRegistrar和注入属性编辑器
<bean id="customEditorRegistrar"
class="com.github.thinwonton.spring.source.analysis.propertyeditor.CustomEditorRegistrar">
<property name="customEditors">
<map>
<entry key="java.util.Date" value-ref="customDateEditor"/>
</map>
</property>
</bean>
<bean id="customDateEditor"
class="com.github.thinwonton.spring.source.analysis.propertyeditor.CustomDateEditor">
<constructor-arg name="allowEmpty" value="true"/>
<constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"/>
</bean>
<bean id="customEditorConfigurer"
class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="customEditorRegistrar"/>
</list>
</property>
</bean>
一切OK。。。